C++新特性36_条件变量的使用(介绍C++11中条件变量的用法;条件变量必须搭配互斥体和条件使用;条件、条件变量、互斥体三胞胎需要同时使用)

接上篇:C++新特性35_条件变量的引入(传统采用while(1)的轮询方式解决线程按顺序执行和共享变量问题十分低效,使用条件变量可以实现高效的事件模型(类似于Qt中信号槽机制))引入了条件变量,用于线程间数据交互或者线程间存在先后顺序的情况,本篇将介绍如何使用条件变量。

条件变量在C++中有一个类,此处先不讲这个类,先讲在新的C++11中条件变量的用法之后再讲C++11中是如何封装的 。

1. 条件变量的用法

下为利用C++11中条件变量的代码:子线程先进入等待,主线程再发送信号

#include <iostream>
#include <string>
#include <mutex>
#include <thread>


/*
//条件变量 C11
_CRTIMP2_PURE int __cdecl _Cnd_init(_Cnd_t *); //初始化 _Cnd_t是作为条件变量对象
_CRTIMP2_PURE void __cdecl _Cnd_destroy(_Cnd_t); //销毁
_CRTIMP2_PURE int __cdecl _Cnd_wait(_Cnd_t, _Mtx_t);//等待
//条件变量是满足事件模型的,就会有通知和响应的过程
//_Cnd_broadcast所有等待的线程都得到了通知
_CRTIMP2_PURE int __cdecl _Cnd_broadcast(_Cnd_t);//广播
//只有某一个线程得到了通知
_CRTIMP2_PURE int __cdecl _Cnd_signal(_Cnd_t);//信号

//互斥体 C11
_CRTIMP2_PURE int __cdecl _Mtx_init(_Mtx_t *, int);
_CRTIMP2_PURE void __cdecl _Mtx_destroy(_Mtx_t);
_CRTIMP2_PURE void __cdecl _Mtx_init_in_situ(_Mtx_t, int);
_CRTIMP2_PURE void __cdecl _Mtx_destroy_in_situ(_Mtx_t);
_CRTIMP2_PURE int __cdecl _Mtx_current_owns(_Mtx_t);
_CRTIMP2_PURE int __cdecl _Mtx_lock(_Mtx_t);
_CRTIMP2_PURE int __cdecl _Mtx_trylock(_Mtx_t);
_CRTIMP2_PURE int __cdecl _Mtx_timedlock(_Mtx_t, const xtime *);
_CRTIMP2_PURE int __cdecl _Mtx_unlock(_Mtx_t);
*/

//全局互斥体,用于同步
//用于发送消息
std::string g_str;

bool g_isSet = false;

//条件变量对象
_Cnd_t g_cnd;
//互斥体
_Mtx_t g_mtx;

//条件变量

// A --> str
// B <-- str

//问题:
//1. 等待的时间过久?
//  轮循:循环
//  条件不成功,则休眠等待

//2. 希望得到通知
//  事件模型,通知方式
//  操作系统(R3 & R0) event事件
//  网络 iocp epoll kevent

// 使用条件变量来完成,当前的线程间通信

//B 子线程

// 利用条件变量,可以控制线程执行的逻辑顺序,也可以用来传递消息
void worker_thread() {

	//std::this_thread::sleep_for(std::chrono::seconds(1));
	_Mtx_lock(g_mtx);
	//子线程接收通知
	while (!g_isSet) {
		//都是原子性:不可以被打断
		//1. 等待g_cnd被通知
		//2. g_mtx 会被释放
		//3. 当等到了g_cnd被触发时,g_mtx会被重新获取
		_Cnd_wait(g_cnd, g_mtx); //陷入等待的过程,等到信号
	}

	printf("%s\r\n", g_str.c_str());

	_Mtx_unlock(g_mtx);
}


//A 主线程
int main() {

	//初始化 对应了构造函数,需要使用的是一个指针 _Cnd_init(_Cnd_t *)
	_Cnd_init(&g_cnd);
	//选择普通互斥体类型
	_Mtx_init(&g_mtx, _Mtx_plain);

	//起一条子线程
	std::thread thd(worker_thread);
	//std::this_thread::sleep_for(std::chrono::seconds(1)); //主线程休眠就可以保证子线程先执行等待主线程的信号

	//主线程发送信号(通知)
	_Cnd_signal(g_cnd);

	_Mtx_lock(g_mtx);
	g_isSet = true;
	g_str = "Hello World!";
	_Mtx_unlock(g_mtx);

	//表示主线程已经准备完毕,通知子线程开始处理
	//主线程发送信号(通知)
	_Cnd_signal(g_cnd);//给条件变量发信号

	thd.join();

	//析构条件变量
	_Cnd_destroy(g_cnd);
	_Mtx_destroy(g_mtx);
	return 0;
}

其中对于_CRTIMP2_PURE int __cdecl _Mtx_init(_Mtx_t *, int);中的第二个参数是前面讲到的互斥体的类型,普通、超时、递归等。
在这里插入图片描述
整体代码的思维导图如下:
在这里插入图片描述

2. 在正常代码中如何使用条件变量发送信号满足逻辑关系?

std::thread thd(worker_thread);起一条子线程,可能在main之前或者之后调,操作系统采用动态的调用回调函数worker_thread(),有可能是在发送信号前也有可能是在发送信号之后。

  • 如果在发送信号之前调用子线程,while (!g_isSet)中g_isSet=false就执行_Cnd_wait(g_cnd, g_mtx);进行等待,
  • 如果在发送信号之后调用子线程,当while (!g_isSet)中g_isSet = true;时,接受信号进行打印
  • 子线程回调是在发送信号前还是在发送信号之后都不会有影响。

条件变量g_cnd一定要搭配互斥体g_mtx使用根本原因是他们在保护一种条件while (!g_isSet),条件变量g_cnd总是搭配一种条件使用,所以将因此将g_cnd称为条件变量;

条件变量g_cnd要被两个线程共享使用,所以需要依赖互斥体,因此条件while (!g_isSet)、 条件变量g_cnd、 互斥体g_mtx三胞胎需要同时使用。

3. 总结

  1. 条件变量:用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知。

  2. 条件变量自身并不包含条件。因为它通常和 if (或者while) 一起用,所以叫条件变量

  3. 需要配合互斥体mtx一起使用

  4. 使用:

  • _Cnd_init/_Cnd_destroy

  • _Cnd_wait :

    • 进入函数前,必须首先获得mtx锁
    • 进入该函数中,线程阻塞,发生:
      • 线程陷入等待,直至条件变量信号被触发
      • 互斥体mtx解锁
    • 线程等待条件变量信号成功,函数即将返回,发生:
      • 在函数返回之前,mtx再次被上锁
  • _Cnd_signal:

    • 解除当前在条件变量上等待的一个线程。 如果没有线程被阻塞,则不执行任何操作并返回。
  • _Cnd_broadcast:

    • 解除当前等待条件变量的所有线程。 如果没有线程被阻塞,则不执行任何操作并返回。

3.学习视频地址: 条件变量的使用

4. 问题

请你说说条件变量?
得分点
线程同步、阻塞、唤醒

标准回答
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。 使用条件变量可以以原子方式阻塞线程,直到某个特定条件为真为止。条件变量始终与互斥锁一起使用,对条件的测试是在互斥锁(互斥)的保护下进行的。如果条件为假,线程通常会基于条件变量阻塞,并以原子方式释放等待条件变化的互斥锁。如果另一个线程更改了条件,该线程可能会向相关的条件变量发出信号,从而使一个或多个等待的线程执行以下操作: 唤醒 再次获取互斥锁 重新评估条件

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十月旧城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值