C++线程/阻塞/同步异步----2

news/2024/4/27 2:44:11/文章来源:https://blog.csdn.net/weixin_42325783/article/details/129155024

本章节内容为记录改写RTK代码时,学习的知识


同步和异步区别

1.定义不同:同步需要将通信双方的时钟统一到一个频率上,异步通信发送的字符间隔时间可以是任意的;
2.准确性不同:同步通信需要比较高精度的精确度,异步则不需要;
3.成本不同:异步通信的设备通常比同步的简单、便宜。


async_read和async_read_some区别

asio::async_read 通常用户读取指定长度的数据,读完或出错才返回。而socket的async_read_some读取到数据或出错就返回,不一定读完了整个包。


tcp/udp同步/异步功能

tcp套接字同步读写:

ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);
ip::tcp::socket sock(service);
sock.connect(ep);
sock.write_some(buffer("GET /index.html\r\n"));
std::cout << "bytes available " << sock.available() << std::endl;
char buff[512];
size_t read = sock.read_some(buffer(buff));

udp套接字同步读写:

ip::udp::socket sock(service);
sock.open(ip::udp::v4());
ip::udp::endpoint receiver_ep("87.248.112.181", 80);
sock.send_to(buffer("testing\n"), receiver_ep);
char buff[512];
ip::udp::endpoint sender_ep;
sock.receive_from(buffer(buff), sender_ep);

udp套接字中异步读取:

using namespace boost::asio;
io_service service;
ip::udp::socket sock(service);
boost::asio::ip::udp::endpoint sender_ep;
char buff[512];
void on_read(const boost::system::error_code & err, std::size_t read_bytes) {std::cout << "read " << read_bytes << std::endl;sock.async_receive_from(buffer(buff), sender_ep, on_read);
}
int main(int argc, char* argv[]) {ip::udp::endpoint ep(ip::address::from_string("127.0.0.1"),
8001);sock.open(ep.protocol());sock.set_option(boost::asio::ip::udp::socket::reuse_address(true));sock.bind(ep);sock.async_receive_from(buffer(buff,512), sender_ep, on_read);service.run();
}

tcp/udp/icmp功能函数

名字TCPUDPICMP
async_read_some--
async_receive_from-
async_write_some--
async_send_to-
read_some--
receive_from-
write_some--
send_to-

阻塞线程的方法

在C中有信号量、互斥量、条件变量、读写锁等可用于线程同步,他们都有对应的可以使之线程阻塞的方法

1.信号量:信号量 (semaphore) 是一种轻量的同步原件,用于制约对共享资源的并发访问。在可以使用两者时,信号量能比条件变量更有效率。

定义于头文件 <semaphore>
counting_semaphore 实现非负资源计数的信号量
binary_semaphore 仅拥有二个状态的信号量(typedef)
  • acquire 减少内部计数器或阻塞到直至能如此
  • try_acquire 尝试减少内部计数器而不阻塞
  • try_acquire_for 尝试减少内部计数器,至多阻塞一段时长
  • try_acquire_until 尝试减少内部计数器,阻塞直至一个时间点

2.互斥:互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。
互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。

定义于头文件 <mutex>
  • lock 锁定互斥,若互斥不可用则阻塞 ,其中lock可锁定多个mutex对象,并内置免死锁算法避免死锁。

  • try_lock尝试锁定互斥,若互斥不可用则返回 (不阻塞)

  • unlock 解锁互斥

    通常不直接使用 std::mutex ,一般使用 std::unique_lock 、 std::lock_guard 或 std::scoped_lock 互斥器管理器使用。

  • lock_guard 实现严格基于作用域的互斥体所有权包装器

  • scoped_lock 用于多个互斥体的免死锁 RAII 封装器

  • unique_lock 实现可移动的互斥体所有权包装器

3.条件变量:条件变量是允许多个线程相互交流的同步原语。它允许一定量的线程等待(可以定时)另一线程的提醒,然后再继续。条件变量始终关联到一个互斥。
定义于头文件 <condition_variable>

condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。
有意修改变量的线程必须
  • 获得 std::mutex (常通过 std::lock_guard )
  • 在保有锁时进行修改
  • 在std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁)
    即使共享变量是原子的,也必须在互斥下修改它,以正确地发布修改到等待的线程。

由上可得,条件变量在线程同步时,需要获得互斥量才能进行。而 unique_lock 这个互斥器管理器允许自由的unlock,所以一般条件变量与unique_lock一起使用。

  • wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行(阻塞)。
    在这里插入图片描述

4.Future:标准库提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常。这些值在共享状态中传递,其中异步任务可以写入其返回值或存储异常,而且可以由持有该引用该共享态的 std::future 或 std::shared_future 实例的线程检验、等待或是操作这个状态。

  • get 返回结果 ,get 方法等待直至 future 拥有合法结果并(依赖于使用哪个模板)获取它。它等效地调用 wait()等待结果。(阻塞)
  • wait 等待结果变得可用 。阻塞直至结果变得可用。调用后 valid() == true 。
  • wait_for等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回。 阻塞直至经过指定的 timeout_duration,或结果变为可用
  • wait_until 等待结果,如果在已经到达指定的时间点时仍然无法得到结果,则返回。阻塞直至抵达指定的 timeout_time,或结果变为可用

5.this_thread:在this_thread命名空间下,有this_thread::sleep_for、this_thread::sleep_until、this_thread::yield 三个方法。

其中前两个可以通过让线程睡眠一段时间而达到阻塞线程的目的。

后者,可以通过一定的条件使得当前线程让度给其他线程达到阻塞的目的。while (condition) this_thread::yield();

6.原子操作(CAS)

此外,使用原子量实现自旋锁,也可以在一定时间内阻塞线程。

class CAS	// 自旋锁
{
private:std::atomic<bool> flag;	// true 加锁、false 无锁
public:CAS() :flag(true) {}   // 注意这里初始化为 true,因此,第一次调用 lock()就会阻塞~CAS() {}CAS(const CAS&) = delete;CAS& operator=(const CAS&) = delete;void lock()	// 加锁{bool expect = false;while (!flag.compare_exchange_strong(expect, true)){expect = false;}}void unlock(){	flag.store(false);}
};

该段引自:https://blog.csdn.net/weixin_43919932/article/details/119985704


仿函数

仿函数是一种强大的编程技术,它最大的优势在于可以将复杂的编程任务变得简单易懂。
例子:用generator_n()来生成10个随机数,其中第三个参数是仿函数。
理解:用结构体或者类的构造函数来当作函数调用,这样在调用时候效率更高。

class Point
{friend ostream& operator<<(ostream& o, const Point& other);
public:Point(int x = 0, int y = 0):_x(x), _y(y){}
private:int _x, _y;
};//仿函数,我们想要一个取值范围为[left, right)的随机点
struct RandPoint
{//把需要传递的参数作为成员变量并用构造函数初始化int _left, _right;RandPoint(int left, int right) :_left(left), _right(right) {}//函数运算符()的重载Point operator()(){//返回[left, right)的随机数的点,先生成[0, right-left),再加left就是[left, right)范围了return Point(rand() % (_right - _left) + _left, rand() % (_right - _left) + _left);}
};ostream& operator<<(ostream& o, const Point& other)
{o << "[" << other._x << "," << other._y << "]";return o;
}int main()
{ list<Point> allPoint(10);  //所有点的数组,初始化时会调用Point的无参构造函数,因为定义有参构造函数时给了默认值,所以会调用自定义的构造函数generate_n(allPoint.begin(), 10, RandPoint(10, 30));  //产生10个随机数的点,最后一个参数是仿函数,把类的匿名对象作为参数传递给generate_n()函数for (Point x : allPoint)  //遍历数组输出所有点{cout << x << " ";}
}

线程状态

在这里插入图片描述

该段引自:https://blog.csdn.net/weixin_43157935/article/details/105872993


async_wait

功能:创建一个io对象,定义一个五秒计时器,时间到了执行handler,异步操作结束前阻塞,结束后返回。

boost::asio::io_service io_service;
boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5)); //定义一个5秒的计时器 ,这里指定的是绝对时间
timer.async_wait(handler); //计时时间一到,开始执行handler函数
io_service.run(); //异步操作结束前堵塞,所有异步结束后返回

C++类型转换之reinterpret_cast

原博主写的非常清晰:https://zhuanlan.zhihu.com/p/33040213

我使用到的是reinterpret_cast,可以直接理解为将16进制的数据强制转换成我们指定的类型即可。

static_cast<类型说明符>(表达式)
dynamic_cast<类型说明符>(表达式)
const_cast<类型说明符>(表达式)
reinterpret_cast<类型说明符>(表达式)

互斥锁std::mutex和std::lock_guard/std::unique_lock

访问共享数据的代码片段称之为临界区(critical section)。

我们现在先只关注最基本的mutex,mutex是最基础的API。其他类都是在它的基础上的改进。所以这些类都提供了下面三个方法,并且它们的功能是一样的:

方法说明
lock()锁定互斥体,如果不可用,则阻塞
try_lock()尝试锁定互斥体,如果不可用,直接返回
unlock()解锁互斥体

这三个方法提供了基础的锁定和解除锁定的功能。使用lock意味着你有很强的意愿一定要获取到互斥体,而使用try_lock则是进行一次尝试。这意味着如果失败了,你通常还有其他的路径可以走。

所以最基本的使用:

std::mutex mtx;void someOp(){mtx.lock(); //加锁... //执行你的操作,这块是临界区mtx.unlock(); //解锁
}

在进入临界区时,执行lock()加锁操作,如果这时已经被其它线程锁住,则当前线程在此排队等待。退出临界区时,执行unlock()解锁操作。

采用“资源分配时初始化(RAII)”方法来加锁、解锁,标准库就提供了下面的这些API,来简化我们手动加锁和解锁的“体力活”。

API说明
lock_guard实现严格基于作用域的互斥体所有权包装器
unique_lock实现可移动的互斥体所有权包装器
锁定策略说明
defer_lock类型为 defer_lock_t,不获得互斥的所有权
try_to_lock类型为try_to_lock_t,尝试获得互斥的所有权而不阻塞
adopt_lock类型为adopt_lock_t,假设调用方已拥有互斥的所有权

lock_guard
所以上边的代码可以变成这样:

std::mutex mtx;void someOp(){std::lock_guard<std::mutex> lock(mtx);; //加锁... //执行你的操作,这块是临界区
}
//出了作用域,自动释放锁mtx

在std::lock_guard对象构造时,传入的mutex对象(即它所管理的mutex对象)会被当前线程锁住。在lock_guard对象被析构时,它所管理的mutex对象会自动解锁,不需要程序员手动调用lock和unlock对mutex进行上锁和解锁操作。lock_guard对象并不负责管理mutex对象的生命周期,lock_guard对象只是简化了mutex对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个lock_guard对象的生命周期内,它所管理的锁对象会一直保持上锁状态;而lock_guard的生命周期结束之后,它所管理的锁对象会被解锁。程序员可以非常方便地使用lock_guard,而不用担心异常安全问题。
unique_lock
unique_lock具有lock_guard的全部功能,但是更加灵活:

1.lock_guard在构造时或者构造前(std::adopt_lock)就已经获取互斥锁,并且在作用域内保持获取锁的状态,直到作用域结束;而unique_lock在构造时或者构造后(std::defer_lock)获取锁,在作用域范围内可以手动获取锁和释放锁,作用域结束时如果已经获取锁则自动释放锁。
2.lock_guard锁的持有只能在lock_guard对象的作用域范围内,作用域范围之外锁被释放,而unique_lock对象支持移动操作,可以将unique_lock对象通过函数返回值返回,这样锁就转移到外部unique_lock对象中,延长锁的持有时间。

所以上边代码也可以这样:

std::mutex mtx;void someOp(){std::unique_lock<std::mutex> lock(mtx, std::defer_lock);; //此时还未加锁lock.lock();//手动获取锁... //执行你的操作,这块是临界区lock.unlock();//手动释放锁
}

另外和条件变量condition_variable一起使用的时候,也需要用unique_lock!

该段引自:https://blog.csdn.net/aiynmimi/article/details/127492406


MutableBufferSequence可变缓冲序列

https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/read.html


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_72409.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5.12 BGP选路原则综合实验

配置BGP的选路原则 1. 实验目的 熟悉BGP的选路的应用场景掌握BGP的选路的配置方法2. 实验拓扑 实验拓扑如图5-11所示: 图5-11:配置BGP的选路原则 3. 实验步骤 (1)配置IP地址 R1的配置

你知道 GO 中的 协程可以无止境的开吗?

GO语言天生高并发的语言&#xff0c;那么是不是使用 go 开辟协程越多越好的&#xff0c;那么在 go 里面&#xff0c;协程是不是可以开无限多个呢&#xff1f; 那么我们就一起来看看尝试写写 demo 吧 尝试开辟尽可能多的 协程 写一个 demo &#xff0c;循环开 1 << 31 …

Mac 安装 homebrew

文章目录1. 简介2. 安装2.1 官方安装2.2 安装 ARM 版 Homebrew2.3 安装 X86 版 Homebrew2.4 多版本共存3. 设置镜像3.1 初次安装brew后配置中科大 zsh3.2 换源配置中科大 zsh3.3 换源清华大学 zsh4. 问题1. 简介 omebrew是一款包管理工具&#xff0c;目前支持macOS和linux系统…

「TCG 规范解读」第10章 TPM工作组 保护你的数字环境

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…

【春秋云境】CVE-2022-28512

靶标介绍&#xff1a; ​ Fantastic Blog (CMS)是一个绝对出色的博客/文章网络内容管理系统。它使您可以轻松地管理您的网站或博客&#xff0c;它为您提供了广泛的功能来定制您的博客以满足您的需求。它具有强大的功能&#xff0c;您无需接触任何代码即可启动并运行您的博客。…

IDEA工具系列之连接Linux

我们在开发的时候&#xff0c;用IDEA开发程序&#xff0c;用XSHELL来管理服务器&#xff0c;这两个工具切换比较麻烦。有没有用IDEA来连接Linux。当然有&#xff0c;下面有实践步骤&#xff1a; 首先&#xff1a;连接Linux 打开IDEA->Tools->Start SSH Session 其中1&…

REDIS09_LBS出现背景、GEO算法介绍、算法步骤、剖析、邻近网格位置推算

文章目录①. LBS出现的背景②. 重新认识经纬度③. 感性认识GeoHash④. Geohash算法介绍⑤. Geohash算法步骤⑥. 更深入剖析GeoHash⑦. 邻近网格位置推算①. LBS出现的背景 ①. 移动互联网时代LBS应用越来越多,所在位置附近三公里的药店、交友软件中附近的小姐姐、外卖软件中附近…

stm32f407探索者开发板(二十)——独立看门狗实验

文章目录一、独立看门狗概述1.1 独立看门狗二、常用寄存器和库函数配置2.1 独立看门狗框图2.2 键值寄存器IWDG_KR2.3 预分频寄存器IWDG_PR2.4 重装载寄存器IWDG_RLR2.5 状态寄存器IWDG_SR2.6 IWDG独立看门狗操作库函数三、手写独立看门狗实验3.1 操作步骤3.2 iwdg.c3.3 iwdg.h3…

NLP学习笔记(九) 分词(上)

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲分词算法 1 概述 分词是自然语言处理领域中的基础任务&#xff0c;是文本预处理的重要步骤 简单来说&#xff0c;就是将文本段落分解为基本语言单位&#xff0c;亦可称之为词元 ( token\text{token}token ) 按照粒度的不…

[Flink]概述day1第4个视频完

一、概述什么是Flink是一种大数据计算引擎&#xff0c;用于对无界&#xff08;流数据&#xff09;和有界&#xff08;批数据&#xff09;数据进行有状态计算。特点1&#xff09;批流一体&#xff1a;统一批处理、流处理2&#xff09;分布式&#xff1a;Flink程序可以运行在多台…

python小基础-更多请自学,或者某某教程-2023-2-21 小扒菜的自学之路【1】

python基础 基础学习 自己跟着菜鸟教程看的一些基础,会java或者js的话,1个半小时就可以over 好久没更新博客了,现在慢慢来发吧,基础内容不太多,自己理解会很快的(下面是一段个人的小经历,大家也可以看看,嘻嘻) 假期看了灵魂摆渡几部电视剧,无聊中收到了一个python爬虫公开课穷,…

单Buffer的缺点与改进方法

单Buffer的缺点与改进方法 文章目录单Buffer的缺点与改进方法一、 单Buffer的缺点二、 使用多Buffer来改进三、 内核驱动程序、APP互相配合使用多buffer致谢参考资料 内核自带的LCD驱动程序 IMX6ULL驱动源码&#xff1a;Linux-4.9.88\drivers\video\fbdev\mxsfb.c 一、 单Buf…

I/O多路复用模型实现——epoll

epoll IO多路复用模型实现机制I/O多路复用epollepoll_create(int size)epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)epoll eventepoll流程I/O多路复用 I/O 多路复用的本质…

Java使用MD5加盐对密码进行加密处理,附注册和登录加密解密处理

前言 在开发的时候&#xff0c;有一些敏感信息是不能直接通过明白直接保存到数据库的。最经典的就是密码了。如果直接把密码以明文的形式入库&#xff0c;不仅会泄露用户的隐私&#xff0c;对系统也是极其的不厉&#xff0c;这样做是非常危险的。 那么我们就需要对这些铭文进…

MyBatis分页插件

目录 分页插件 Mybatis插件典型适用场景 实现思考 第一个问题 第二个问题 自定义分页插件 分页插件使用 添加pom依赖 插件注册 调用 代理和拦截是怎么实现的 PageHelper 原理 分页插件 MyBatis 通过提供插件机制&#xff0c;让我们可以根据自己的需要去增强MyBati…

去中心化开源社交平台Misskey

本文是应网友 anthony084 的要求写的&#xff1b; 什么是 Misskey &#xff1f; Misskey 是一个开源、去中心化的社交媒体平台&#xff0c;发帖方式类似于微博和推特。 去中心化则意味着一个 Misskey 实例可以与其他 Misskey 实例进行相互连接&#xff0c;在 Fediverse (Activi…

广义学习矢量量化(GLVQ)分类算法介绍和代码实现

广义学习矢量量化&#xff08;Generalized Learning Vector Quantization&#xff0c;GLVQ&#xff09;是一种基于原型的分类算法&#xff0c;用于将输入数据分配到先前定义的类别中。GLVQ是LVQ&#xff08;Learning Vector Quantization&#xff09;的一种扩展形式&#xff0c…

性能分析之vmstat工具

vmstat 工具的使用 命令&#xff1a;vmstat 1 60> /tmp/cpu.txt 说明&#xff1a;每秒采样 1 次&#xff0c;共采集 100 次 格式化显示&#xff1a;cat /tmp/cpu.txt|column -t &#xff08;1&#xff09;procs r&#xff1a; 表示运行和等待 CPU 时间片的进程数&#xff0…

go进阶(1) -深入理解goroutine并发运行机制

并发指的是同时进行多个任务的程序&#xff0c;Web处理请求&#xff0c;读写处理操作&#xff0c;I/O操作都可以充分利用并发增长处理速度&#xff0c;随着网络的普及&#xff0c;并发操作逐渐不可或缺 一、goroutine简述 在Golang中一个goroutines就是一个执行单元&#xff…

多种调度模式下的光储电站经济性最优储能容量配置分析(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…