1. condition_variable介绍

在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作;当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。

其主要成员函数如下

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:

  • 一个线程因等待"条件变量的条件成立"而挂起;

  • 另外一个线程使"条件成立",给出信号,从而唤醒被等待的线程。

为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;通常情况下这个锁是std::mutex,并且管理这个锁只能是 std::unique_lockstd::mutex RAII模板类。

上面提到的两个步骤,分别是使用以下两个方法实现:

  • 等待条件成立使用的是condition_variable类成员wait 、wait_for 或 wait_until。

  • 给出信号使用的是condition_variable类成员notify_one或者notify_all函数。

2. 使用说明

在条件变量中只能使用std::unique_lock<std::mutex>说明

unique_lock和lock_guard都是管理锁的辅助类工具,都是RAII风格;它们是在定义时获得锁,在析构时释放锁。它们的主要区别在于unique_lock锁机制更加灵活,可以再需要的时候进行lock或者unlock调用,不非得是析构或者构造时。它们的区别可以通过成员函数就可以一目了然。在这里插入图片描述

2.1. wait/wait_for

线程的阻塞是通过成员函数wait()/wait_for()/wait_until()函数实现的。这里主要说明前面两个函数:

2.1.1. wait()成员函数

函数声明如下:

void wait(std::unique_lock<std::mutex>& lock);
//Predicate 谓词函数,可以普通函数或者lambda表达式
template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);

wait 导致当前线程阻塞直至条件变量被通知,或虚假唤醒发生,可选地循环直至满足某谓词。

2.1.2. wait_for()成员函数

函数声明如下:

template<class Rep, class Period>
std::cv_status wait_for(std::unique_lock<std::mutex>& lock,const std::chrono::duration<Rep, Period>& rel_time);
template<class Rep, class Period, class Predicate>
bool wait_for(std::unique_lock<std::mutex>& lock,const std::chrono::duration<Rep, Period>& rel_time,Predicate pred);

wait_for 导致当前线程阻塞直至条件变量被通知,或虚假唤醒发生,或者超时返回。

返回值说明:

  1. 若经过 rel_time 所指定的关联时限则为 std::cv_status::timeout ,否则为 std::cv_status::no_timeout 。

  2. 若经过 rel_time 时限后谓词 pred 仍求值为 false 则为 false,否则为 true 。

以上两个类型的wait函数都在会阻塞时,自动释放锁权限,即调用unique_lock的成员函数unlock(),以便其他线程能有机会获得锁。这就是条件变量只能和unique_lock一起使用的原因,否则当前线程一直占有锁,线程被阻塞。

2.2. notify_all/notify_one

notify函数声明如下:

void notify_one() noexcept;

若任何线程在 *this 上等待,则调用 notify_one 会解阻塞(唤醒)等待线程之一。

void notify_all() noexcept;

若任何线程在 *this 上等待,则解阻塞(唤醒)全部等待线程。

2.3. 虚假唤醒

在正常情况下,wait类型函数返回时要么是因为被唤醒,要么是因为超时才返回,但是在实际中发现,因此操作系统的原因,wait类型在不满足条件时,它也会返回,这就导致了虚假唤醒。因此,我们一般都是使用带有谓词参数的wait函数,因为这种(xxx, Predicate pred )类型的函数等价于:

while (!pred()) //while循环,解决了虚假唤醒的问题
{wait(lock);
}

原因说明如下:

假设系统不存在虚假唤醒的时,代码形式如下:

if (不满足xxx条件) {//没有虚假唤醒,wait函数可以一直等待,直到被唤醒或者超时,没有问题。//但实际中却存在虚假唤醒,导致假设不成立,wait不会继续等待,跳出if语句,//提前执行其他代码,流程异常wait();
}//其他代码
...

正确的使用方式,使用while语句解决:

while (!(xxx条件) )
{//虚假唤醒发生,由于while循环,再次检查条件是否满足,//否则继续等待,解决虚假唤醒wait();
}
//其他代码
....

3. 使用示例

3.1. 生产者消费者问题

生产者消费者问题,也称有限缓冲问题,是一个多进程/线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个进程/线程——即所谓的“生产者”和“消费者”,在实际运行时会发生的问题。

生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。

同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。

#include <mutex>
#include <deque>
#include <iostream>
#include <thread>
#include <condition_variable>class PCModle {public:PCModle() : work_(true), max_num(30), next_index(0) {}void producer_thread() {while (work_) {std::this_thread::sleep_for(std::chrono::milliseconds(500));{//加锁std::unique_lock<std::mutex> lk(cvMutex);//当队列未满时,继续添加数据cv.wait(lk, [this]() { return this->data_deque.size() <= this->max_num; });}next_index++;data_deque.push_back(next_index);std::cout << "producer " << next_index << ", queue size: " << data_deque.size() << std::endl;//唤醒其他线程cv.notify_all();//自动释放锁}}void consumer_thread() {while (work_) {{//加锁std::unique_lock<std::mutex> lk(cvMutex);//检测条件是否达成cv.wait(lk, [this] { return !this->data_deque.empty(); });}//互斥操作,消息数据int data = data_deque.front();data_deque.pop_front();std::cout << "consumer " << data << ", deque size: " << data_deque.size() << std::endl;//唤醒其他线程cv.notify_all();//自动释放锁}}private:bool work_;std::mutex cvMutex;std::condition_variable cv;//缓存区std::deque<int> data_deque;//缓存区最大数目size_t max_num;//数据int next_index;
};int main() {PCModle obj;std::thread ProducerThread = std::thread(&PCModle::producer_thread, &obj);std::thread ConsumerThread = std::thread(&PCModle::consumer_thread, &obj);ProducerThread.join();ConsumerThread.join();return 0;
}

运行结果:

3.2. 多线程抢占

std::mutex mtx;
std::condition_variable cv;
void print_id (int id) {std::unique_lock<std::mutex> lck(mtx);// 首先执行到这一句的第一个线程会lck.lock(), 其他线程则会卡在这一句;cv.wait(lck);/*这个函数比较有意思,当执行到这一行的时候,他会lck.unlock(), 然后条件变量接替开始阻塞,这样第二个线程就会执行上一句lck初始化,然后所有线程都会走一遍lck.lock()->lck.unlock()->阻塞,然后所有线程都停留在这个位置,等待通知,最先得到通知的线程会调用lck.lock()(至于谁先得到通知线程去竞争了),这样就保证下面这句命令操作的原子性*/std::cout << "thread " << id << '\n';
}//类似于只能指针,lck自动unlockvoid go() {std::unique_lock<std::mutex> lck(mtx);cv.notify_all();
}int main() {std::thread threads[10];// spawn 10 threads:for (int i=0; i<10; ++i) threads[i] = std::thread(print_id,i);std::cout << "10 threads ready to race...\n";go();//主线程lck.lock()->通知所有wait->                      for (auto& th : threads) {th.join();}return 0;
}

3.3. 虚假唤醒


std::mutex mtx;
std::condition_variable cv;
bool ready = false;void print_id (int id)
{std::unique_lock<std::mutex> lck(mtx);while (!ready) cv.wait(lck);std::cout << "thread " << id << '\n';
}void go() {std::unique_lock<std::mutex> lck(mtx);ready = true;cv.notify_all();
}

参考文献

C++条件变量_feng__shuai的博客-CSDN博客_c++条件变量

C++11条件变量使用详解_Keep Moving~-CSDN博客_c++ 条件变量

C++条件变量使用详解相关推荐

  1. C 条件变量使用详解

    condition_variable介绍 在C 11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作:当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些 ...

  2. C++11条件变量使用详解

    condition_variable介绍 在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作:当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这 ...

  3. 相关性质和条件变量-ReentrantLock详解(2)-AQS-并发编程(Java)

    文章目录 1 可重入 2 可打断 3 公平锁 4 条件变量 4.1 await() 4.1.1 主方法 4.1.2 addConditionWaiter() 4.1.3 isOnSyncQueue() ...

  4. ThinkPHP模版引擎之变量输出详解

    ThinkPHP模版引擎之变量输出详解 使用ThinkPHP开发有一定时间了,今日对ThinkPHP的模板引擎变量解析深入了解了一下,做出一些总结,分享给大家供大家参考.具体分析如下: 我们已经知道了 ...

  5. python变量类型-Python 变量类型详解

    变量存储在内存中的值.这就意味着在创建变量时会在内存中开辟一个空间. 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中. 因此,变量可以指定不同的数据类型,这些变量可以存储整 ...

  6. Nginx内置变量以及日志格式变量参数详解

    Nginx内置变量以及日志格式变量参数详解 $args #请求中的参数值 $query_string #同 $args $arg_NAME #GET请求中NAME的值 $is_args #如果请求中有 ...

  7. on 和where条件的放置详解

    on 和where条件的放置详解 on 和where条件的放置详解 总结: 对于left join,不管on后面跟什么条件,左表的数据全部查出来,因此要想过滤需把条件放到where后面 对于inner ...

  8. python的常量和变量_python中的常量和变量代码详解

    局部和全局变量: # name='lhf' # def change_name(): # # global name # name='帅了一比' # print('change_name',name) ...

  9. python 数列筛选_对numpy中的数组条件筛选功能详解

    在程序设计中,时常会遇到数据的唯一化.相同.相异信息的提取等工作,在格式化的向量存储矩阵中南,numpy能够提供比较不错的快速处理功能. 1,唯一化的实现: In [63]: data = np.ar ...

最新文章

  1. 适用于OpenGL离屏渲染上下文的初始化代码
  2. java贪吃蛇教程_用Java做的贪吃蛇,简单版......
  3. 【首发】徐亦达团队新论文推荐:模限界矩阵分解
  4. 数据分析之超市订单分析
  5. Linux 中 ctime,mtime,atime 的区别
  6. (一看就懂)傅里叶变换、拉普拉斯变换、Z变换、卷积的经典文章汇总
  7. linux ssh和sftp区别,Linux SSH和SFTP配置
  8. 23种设计模式(5)-适配器模式
  9. Java基础(四) StringBuffer、StringBuilder原理浅析
  10. flask 导出excel
  11. ui web php,Uimaker 一个精美的后台管理系统模版,可使用在web 中 WEB(ASP,PHP,...) 262万源代码下载- www.pudn.com...
  12. CATIA汽车焊装夹具设计基础入门视频教程
  13. leetcode13——罗马数字转整数(简单,0)
  14. oracle单行子查询返回多个行 order by,单行子查询返回多个行
  15. Anaconda3 python3.7安装Django稀里糊涂终于successful法
  16. Python 制作迷宫游戏(二)——游戏窗口
  17. 【js逆向】md5加密参数破解
  18. js打印三角形超详解
  19. Ubuntu20.04安装Docker以及Docker安装微信、企业微信、Docker常用命令
  20. C语言必背经典程序代码

热门文章

  1. 微软公布Win10正式版功能对比表,哪个版本适合你?
  2. Linux--文件管理以及权限的修改
  3. 全球物联网产业规模不断扩大 中国市场前景分析
  4. 转:C#中Monitor对象与Lock关键字的区别分析
  5. javascript与java的不同之处
  6. CSS中background-position属性
  7. 错误信息输出,重定向到文件
  8. Utilize Sql Tuning Advisor from Script
  9. Linux下vi编辑器命令精华版
  10. 编码和编码格式一样吗?_学习如何像专业人士一样编码