C++ condition_variable 条件变量
本节来了解下C++11 中关于条件变量(condition_variable) 的相关知识,这一部分的内容相信网上已经有了很多的分享,这里仅是对该部分内容学习的记录、总结。
条件变量(condition_variable)
条件变量是一种多线程的同步机制,它能够阻塞线程,直到某一条件满足。条件变量要与互斥量联合使用,以避免出现竞争的情况,当调用condition_variable的一个等待函数时,它使用一个unique_lock对象来锁定线程。
PS:为什么有了lock_guard,还需要
unique_lock?
虽然lock_guard挺好用的,但是有个很大的缺陷,在定义lock_guard的地方会调用构造函数加锁,在离开定义域的话lock_guard就会被销毁,调用析构函数解锁。这就产生了一个问题,如果这个定义域范围很大的话,那么锁的粒度就很大,很大程序上会影响效率。
条件变量作用
条件变量的作用是用于多线程之间关于共享数据状态变化的同学。当一个动作需要另外一个动作完成时才能进行,即:当一个线程的行为依赖于另外一个线程对共享数据状态的改变时,这时候就可以使用条件变量。
条件变量是与互斥相关联的一种用于多线程之间关于共享数据状态改变的通信机制。 它将解锁与挂起封装成原子操作。等待一个条件变量时,会解开与该条件变量相关的锁,因此,使用条件变量等待的前提之一就是保证互斥量加锁。该互斥量会被自动加锁,所以,在完成操作之后需要解锁。
底层实现
class condition_variable {
public:
typedef _Cnd_t native_handle_type;
condition_variable() { // 构造函数,初始化条件变量,所有的条件变量必须初始化后才能使用。
_Cnd_initX(&_Cnd);
}
~condition_variable() _NOEXCEPT { // 析构函数
_Cnd_destroy(&_Cnd);
}
condition_variable(const condition_variable&) = delete;
condition_variable& operator=(const condition_variable&) = delete;
void notify_one() _NOEXCEPT { // 唤醒一个在等待线程
_Cnd_signalX(&_Cnd);
}
void notify_all() _NOEXCEPT { // 唤醒所有在等待的线程
_Cnd_broadcastX(&_Cnd);
}
void wait(unique_lock<mutex>& _Lck) { // 等待
_Cnd_waitX(&_Cnd, &_Lck.mutex()->_Mtx);
}
template<class _Predicate>
void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) { // 等待,带有描述式
while (!_Pred())
wait(_Lck);
}
template<class _Rep, class _Period>
_Cv_status wait_for(unique_lock<mutex>& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time) {
stdext::threads::xtime _Tgt = _To_xtime(_Rel_time);
return (wait_until(_Lck, &_Tgt));
}
template<class _Rep, class _Period, class _Predicate>
bool wait_for(unique_lock<mutex>& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time, _Predicate _Pred) {
stdext::threads::xtime _Tgt = _To_xtime(_Rel_time);
return (wait_until(_Lck, &_Tgt, _Pred));
}
template<class _Clock, class _Duration>
_Cv_status wait_until( unique_lock<mutex>& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time) {
typename chrono::time_point<_Clock, _Duration>::duration
_Rel_time = _Abs_time - _Clock::now();
return (wait_for(_Lck, _Rel_time));
}
template<class _Clock, class _Duration, class _Predicate>
bool wait_until(unique_lock<mutex>& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time, _Predicate _Pred) {
typename chrono::time_point<_Clock, _Duration>::duration
_Rel_time = _Abs_time - _Clock::now();
return (wait_for(_Lck, _Rel_time, _Pred));
}
_Cv_status wait_until( unique_lock<mutex>& _Lck, const xtime *_Abs_time) {
if (!_Mtx_current_owns(&_Lck.mutex()->_Mtx))
_Throw_Cpp_error(_OPERATION_NOT_PERMITTED);
int _Res = _Cnd_timedwaitX(&_Cnd, &_Lck.mutex()->_Mtx, _Abs_time);
return (_Res == _Thrd_timedout ? cv_status::timeout : cv_status::no_timeout);
}
template<class _Predicate>
bool wait_until(unique_lock<mutex>& _Lck, const xtime *_Abs_time, _Predicate _Pred) {
bool _Res = true;
while (_Res && !_Pred())
_Res = wait_until(_Lck, _Abs_time)
!= cv_status::timeout;
return (_Pred());
}
native_handle_type native_handle() { // 返回条件变量的句柄
return (_Cnd);
}
void _Register(unique_lock<mutex>& _Lck, int *_Ready) {
_Cnd_register_at_thread_exit(&_Cnd, &_Lck.release()->_Mtx, _Ready);
}
void _Unregister(mutex& _Mtx) {
_Cnd_unregister_at_thread_exit(&_Mtx._Mtx);
}
private:
_Cnd_t _Cnd;
};
在类condition_variable中,包含了私有成员变量、构造函数、析构函数、等待和唤醒等方法。
等待操作: wait()、wait_for()、wait_until()
唤醒操作: notify_one()、notify_all()
条件变量的使用
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
using namespace std;
mutex mtx; // 互斥量
condition_variable cv; // 条件变量
bool ready = false; // 标志量
void print_id(int id) {
unique_lock<mutex> lck(mtx); // 上锁
while (!ready) {
cv.wait(lck); // 线程等待直到被唤醒(释放锁 + 等待,唤醒,在函数返回之前重新上锁)
}
cout << "thread " << id << '\n';
}
void go() {
unique_lock<mutex> lck(mtx); // 上锁
ready = true;
cv.notify_all(); // 唤醒所有正在等待(挂起)的线程(在这里面要释放锁,为了在wait函数返回之前能成功的重新上锁)
}
int main() {
thread threads[10];
for (int i = 0; i<10; ++i) {
threads[i] = thread(print_id, i);
}
cout << "10 threads ready to race...\n";
go();
for (auto& th : threads) {
th.join();
}
return 0;
}
来分析一下多线程代码时如何运行的:
1、线程A一来,就将互斥量上锁(持有了锁),ready为false,那么线程A将调用条件变量的wait()方法;
2、在wait()方法中,做的第一件事就是将互斥量解锁(释放持有权),并进入等待状态(在wait()中阻塞,线程A挂起);
3、现在线程B来了,互斥量是没有上锁的,所以线程B能持有锁,同理,接下来线程B也会挂起;
4、当所有线程都挂起了(就绪),此时互斥量也没有被上锁,在主线程中将ready置为true,并调用notify_all()将所有挂起的线程都唤醒;
5、此时所有线程将从wait()方法中返回,比如线程C先返回,在return之前,wait()方法做的最后一件事就是自动将互斥量上锁(线程C重新持有锁,以配合unique_lock的析构函数);
6、由于while循环,此时再判断到ready为true,那么线程C将执行打印id的语句,由于此时只有线程C持有锁,不存在线程竞争问题,执行完打印之后,线程C就结束了,此时由unique_lock的析构函数解锁,释放所有权。
7、由于在wait()方法return之前,会自动重新去持有锁,若此时锁由线程C持有,则其他线程将继续阻塞,直到线程C释放锁;若线程C执行完毕后释放了锁,那么其他线程将会争取锁的持有权,争取到锁的就会像之前的线程C一样;没有争取到的就继续阻塞;
8、以此类推,由于每个线程都join,那么当所有线程执行完毕后,主线程才会继续执行;
实际上,条件变量的wait()、wait_for()、wait_until()方法中所作的事是:解锁 + 等待、唤醒、加锁,这三个是有序发生的。
GCC源码位置:http://ftp.gnu.org/gnu/gcc/
参考:《探索C++多线程》:condition_variable源码(一)
C++ condition_variable 条件变量的更多相关文章
- C++11并发——多线程条件变量std::condition_variable(四)
https://www.jianshu.com/p/a31d4fb5594f https://blog.csdn.net/y396397735/article/details/81272752 htt ...
- C++11并行编程-条件变量(condition_variable)详细说明
<condition_variable >头文件主要包含有类和函数相关的条件变量. 包括相关类 std::condition_variable和 std::condition_variab ...
- C++并发编程 条件变量 condition_variable,线程安全队列示例
1. 背景 c++11中提供了对线程与条件变量的更好支持,对于写多线程程序方便了很多. 再看c++并发编程,记一下学习笔记. 2. c++11 提供的相关api 3.1 wait wait用于无条件等 ...
- Windows:C++11并发编程-条件变量(condition_variable)详解
<condition_variable >头文件主要包含了与条件变量相关的类和函数.相关的类包括 std::condition_variable和 std::condition_varia ...
- 八、条件变量std::condition_variable、wait()、notify_one()、notify_all(粗略)
一.std::condition_variable 用在多线程中. 线程A:等待一个条件满足 线程B:专门在消息队列中扔消息,线程B触发了这个条件,A就满足条件了,可以继续执行 std::condit ...
- 条件变量 condition_variable wait_until
wait_until(阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点) #include <iostream> #include <atomic> #include & ...
- 条件变量 condition_variable wait_for
wait_for(阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后) #include <iostream> #include <atomic> #include < ...
- 条件变量 condition_variable wait
wait(阻塞当前线程,直到条件变量被唤醒) #include <iostream> #include <string> #include <thread> #in ...
- C++11 mutex unique_lock condition_variable 互斥锁 条件变量
创建项目再进行测试比较麻烦,可以使用这个在线编译器进行验证,快速方便 C++11在线编译器 mutex是互斥锁,互斥量 condition_variable是条件变量 std::mutex m; vo ...
- 第8章 用户模式下的线程同步(4)_条件变量(Condition Variable)
8.6 条件变量(Condition Variables)——可利用临界区或SRWLock锁来实现 8.6.1 条件变量的使用 (1)条件变量机制就是为了简化 “生产者-消费者”问题而设计的一种线程同 ...
随机推荐
- QT5笔记:9. QT的容器类
QList 中存放对象指针,QVector直接存放对象,所以访问性能更高 QMap中key不可以重复,QMultiMap中key可以重复 QMap在内存中顺序存储,QHash不是顺序存储的(hash算 ...
- Easyexcel(1-注解使用)
版本依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</a ...
- 性能对比实验折线图绘制代码(YOLO系列为例)
本文用于绘制性能折线图,适用于对比实验,发现很多博文都是收费,欺负哥们懒得学习,一气之下ai了一下再进行代码修改,免费供给大家学习参考,便于大家撰写论文数据时利于绘制图像. import pandas ...
- 如何让低于1B参数的小型语言模型实现 100% 的准确率
如何让低于1B参数的小型语言模型实现 100% 的准确率 上下文学习被低估了--ICL 是提升性能的秘密钥匙--教会 AI 说"我不知道"--第 2 部分 Fabio Matric ...
- JdbcTemplate 自定义返回的结果集字段和实体类映射
废话不多:抄袭代码 package com.webank.wedatasphere.qualitis.handler; import com.webank.wedatasphere.qualitis. ...
- Radmin远程自动登入管理工具
功能说明: Radmin远程自动登入管理工具,服务器登入密码采用加密方式存储,软件可添加,编辑,删除服务器列表以及扫描服务器是否在线. 连接方式:有完全控制,仅查看,文件传输,关机等功能. 使用说明: ...
- Delphi 使用API函数AnimateWindow实现窗体特效功能
API函数 AnimateWindow 使用: 函数功能:窗体显示和隐藏时产生特殊的动画效果:可以产生两种类型的动画效果: 滚动动画 和 滑动动画 函数原型:BOOL AnimateWindow(HW ...
- dxTabbedMDIManager1关闭窗体
procedure TfrmJianKongXinXi.FormClose(Sender: TObject; var Action: TCloseAction);begin Action:=caFre ...
- AI时代:本地运行大模型vllm
https://docs.vllm.ai/en/latest/index.html 高吞吐量.高内存效率的 LLMs 推理和服务引擎(快速搭建本地大模型,且openAI API 兼容) vLLM is ...
- 大模型微调实战:通过 LoRA 微调修改模型自我认知
本文主要分享如何使用 LLaMAFactory 实现大模型微调,基于 Qwen1.5-1.8B-Chat 模型进行 LoRA 微调,修改模型自我认知. 本文的一个目的:基于 Qwen1.5-1.8B- ...