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

news2024/11/30 2:37:14

本章节内容为记录改写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.coloradmin.cn/o/363695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法基础】栈与队列

一、栈1.1 模拟栈实现一个栈&#xff0c;栈初始为空&#xff0c;支持四种操作&#xff1a;push x – 向栈顶插入一个数 x&#xff1b;pop – 从栈顶弹出一个数&#xff1b;empty – 判断栈是否为空&#xff1b;query – 查询栈顶元素。现在要对栈进行 M 个操作&#xff0c;其中…

【2023最火教程】Python性能测试框架Locust实战教程(建议收藏)

01、认识Locust Locust是一个比较容易上手的分布式用户负载测试工具。它旨在对网站&#xff08;或其他系统&#xff09;进行负载测试&#xff0c;并确定系统可以处理多少个并发用户&#xff0c;Locust 在英文中是 蝗虫 的意思&#xff1a;作者的想法是在测试期间&#xff0c;放…

图解 paxos 论文《The Part-Time Parliament》

本文以图文并茂的方式重新演绎 Paxos 开山之作 《The Part-Time Parliament》[1]&#xff0c;并尝试解释原论文中语焉不详的地方。 背景 在 Paxos 小岛上&#xff0c;施行着一种 Parliament(议会) 政治。小岛上执行的所有 decree(法令) 都需要先由 Parliament 在 Chamber 内表…

leetcode 21~30 学习经历

leetcode 21~30 学习经历21. 合并两个有序链表22. 括号生成23. 合并K个升序链表24. 两两交换链表中的节点25. K 个一组翻转链表26. 删除有序数组中的重复项27. 移除元素28. 找出字符串中第一个匹配项的下标29. 两数相除30. 串联所有单词的子串小结21. 合并两个有序链表 将两个升…

15. Qt中OPenGL的参数传递问题

1. 说明 在OPenGL中&#xff0c;需要使用GLSL语言来编写着色器的函数&#xff0c;在顶点着色器和片段着色器之间需要参数值的传递&#xff0c;且在CPU中的数据也需要传递到顶点着色器中进行使用。本文简单介绍几种参数传递的方式&#xff1a; &#xff08;本文内容仅个人理解&…

学校节能减排实施方案-浅析高校能耗及节能管理

摘要:高校能源管理是高校治理体系和高校后勤保障的重要组成部分。利用数据统计等手段对蚌埠医学院近年来的能源使用情况进行统计分析&#xff0c;通过横向及纵向对比&#xff0c;结合国家相关政策法规及同类高校能耗情况&#xff0c;对该校能源消耗现状进行了综合分析&#xff…

Web3中文|2023年zk赛道爆发,即将推出的Polygon zkEVM有多重要?

Crypto行业被广泛关注的零知识证明&#xff08;Zero—Knowledge Proof&#xff09;技术&#xff0c;在1980年就被数学家S.Goldwasser、S.Micali及C.Rackoff提出。 零知识证明涉及一系列步骤&#xff0c;可以实现密码学中的「可用而不可知」。 而区块链有着公开透明、不可篡改…

Nginx之反向代理、负载均衡、动静分离。

Nginx之反向代理、负载均衡、动静分离。 1、Nginx是啥&#xff1f; 轻量级Web服务器、反向代理服务器、电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器 在 BSD-like 协议下发行、占内存少、并发高&#xff08;同时处理请求能力&#xff09;。 2、安装 官网&#xf…

【Python】序列与列表(列表元素的增删改查,求之,列表推导式、列表的拷贝)

一、序列序列的概念&#xff1a;按照某种顺序排列的数据类型就叫做序列&#xff0c;比如字符串&#xff0c;列表&#xff0c;元组&#xff0c;集合序列的共同点是都有下标&#xff0c;支持index()方法和count()&#xff0c;也支持切片处理(等同于字符串序列的切片处理)l1 [0, …

Leetcode(每日一题)——1140. 石子游戏 II

摘要 ​​​​​​1140. 石子游戏 II 877. 石子游戏 1406. 石子游戏 III 375. 猜数字大小 II 464. 我能赢吗 486. 预测赢家 1025. 除数博弈 一、动态规划解析 Alice一开始有两个选择&#xff1a;拿前一堆/前两堆石子。如果 Alice 拿前一堆&#xff0c;那么轮到 Bob 时…

Propargyl-PEG1-SS-PEG1-PFP ester,1817735-30-0,炔基应用于生物标记

【中文名称】丙炔-单乙二醇-二硫键-单乙二醇-五氟苯酚酯【英文名称】 Propargyl-PEG1-SS-PEG1-PFP ester【结 构 式】【CAS号】1817735-30-0【分子式】C16H15F5O4S2【分子量】430.4【基团部分】炔基基团【纯度标准】95%【包装规格】1g&#xff0c;5g&#xff0c;10g&#xff0c…

互联网行业中,哪些岗位越老越吃香?

你是不是也想转行IT行业&#xff0c;找一门适合自己学习&#xff0c;能拿高薪的技术&#xff0c;最好还越老越吃香&#xff1f;或许先应该看看对应岗位&#xff0c;老资格的同行们可以拿到的薪资数再做判断。整体上看&#xff0c;大部分岗位的起薪水平相差不大&#xff0c;但随…

matplotlib绘制三维图

目录线状堆积图 PolygonPlot三维表面图 SurfacePlot散点图ScatterPlot柱形图 BarPlot三维直方图螺旋曲线图 LinePlotContourPlot轮廓图网状图 WireframePlot箭头图二维三维合并文本图Text三维多个子图线状堆积图 PolygonPlot Axes3D.add_collection3d(col, zs0, zdir‘z’)  …

(考研湖科大教书匠计算机网络)第六章应用层-第一、二节:应用层概述和C/S及P2P

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;应用层概述二&#xff1a;客户/服务器&#xff08;C/S&#xff09;和对等&#xff08;P2P&#xff09;方式&#xff08;1&#xff09;客户/服务器&…

Vue页面组成及常用属性

一、Vue页面组成 目前的项目中&#xff0c;Vue页面都是采用组件套娃的形式&#xff0c;由一个一个的组件拼接而成整个页面。一个组件就是一个.vue文件。组件通常由template和script两部分组成&#xff1a; template部分&#xff1a;页面展示的具体元素内容&#xff0c;比如文字…

Type-c诱骗取电芯片大全

随着Type-C的普及和推广&#xff0c;目前市面上的电子设备正在慢慢淘汰micro-USB接口&#xff0c;逐渐都更新成了Type-C接口&#xff0c;micro-USB接口从2007年上市&#xff0c;已经陪伴我们走过十多个年头&#xff0c;如今也慢慢退出舞台。 今天我们评测的产品是市面上Type-C…

【OJ】最小字典序游戏

&#x1f4da;Description: 牌王和图王在玩一个游戏。 他们需要轮流移动字符串上的L&#xff0c;R指针&#xff0c;最后一位无法移动的人会输掉游戏。 给定一个字符串 s &#xff0c;起初有两个指针 L 和 R 都指向字符串的下标为k的位置(1 < k < | s |&#xff0c;|s…

CCNP350-401学习笔记(501-550题)

501、Refer to the exhibit. What is the effect of the configuration? A. The device will allow users at 192.168.0.202 to connect to vty lines 0 through 4 using the password ciscotestkey B. The device will allow only users at 192 168.0.202 to connect to vty …

Mybatis-Plus入门系列(20) -兼容多种数据库

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录前言方案分析1. 分页2. XML自定义SQL案例演示1. 配置2. 简单分页查询3. 带方言的分页查询参考前言 在我们实际开发软件产品过程中&#xff0c;数据库的类型可能不是确定的&#xff0c;也有客户…

PHP面试题

PHP相关 php7新特性 1.类型的声明 php7可以声明函数传参的类型和返回值的类型&#xff0c;比如可以用int&#xff0c;string声明参数和返回值的类型&#xff0c;如下&#xff1a; 代码&#xff1a;declare(strict_types1); function add(int $a,int $b):int{ return $a$b;…