C++11之多线程(三、条件变量)

C++11之多线程(三、条件变量)

文章目录

1. C++11之多线程系列文章目录2. 条件变量(Condition Variable)2.1. 条件变量为什么叫条件变量?2.2. 为什么条件变量需要和锁一起使用?

C++11之多线程系列文章目录一、标准库的线程封装类Thread和Future二、互斥对象(Mutex)和锁(Lock)三、条件变量(Condition Variable)四、原子操作(Atomic Operation)五、内存序(Memory Order)

条件变量(Condition Variable)条件变量是一种同步原语(Synchronization Primitive)用于多线程之间的通信,它可以阻塞一个或同时阻塞多个线程直到:

收到来自其他线程的通知

超时

发生虚假唤醒(Spurious Wakeup)

C++11为条件变量提供了两个类

std::condition_variable:必须与std::unique_lock配合使用

std::condition_variable_any:更加通用的条件变量,可以与任意类型的锁配合使用,相比前者使用时会有额外的开销

二者具有相同的成员函数

成员函数

说明

notify_one

通知一个等待线程

notify_all

通知全部等待线程

wait

阻塞当前线程直到被唤醒

wait_for

阻塞当前线程直到被唤醒或超过指定的等待时间(长度)

wait_until

阻塞当前线程直到被唤醒或到达指定的时间(点)

二者在线程要等待条件变量前都必须要获得相应的锁

条件变量为什么叫条件变量?

条件变量存在虚假唤醒的情况,因此在线程被唤醒后需要检查条件是否满足

无论是notify_one或notify_all都是类似于发出脉冲信号,如果对wait的调用发生在notify之后是不会被唤醒的,所以接收者在使用wait等待之前也需要检查条件(标识)是否满足,另一个线程(通知者)在nofity前需要修改相应标识供接收者检查

条件变量因此得名。

为什么条件变量需要和锁一起使用?观察std::condition_variable::wait函数,发现它的两个重载都必须将锁作为参数

123void wait(std::unique_lock& lock);template< class Predicate >void wait(std::unique_lock& lock, Predicate pred);

首先考虑wait函数不需要锁作为参数的情况,下面的代码中flag初始化为false,线程A将flag置为true并使用notify_one发出通知,线程B使用while循环在wait前后都会检查flag,直到flag被置为true才往下执行。

123456789101112131415// Thread A{ std::unique_lock lck(mt); flag = true;}cv.notify_one();// Thread Bauto pred = []() { std::unique_lock lck(mt); return flag;};while(!pred()) { cv.wait();}

如果两个线程的执行顺序为:

线程B检查flag发现其值为false

线程A将flag置为true

线程A使用notify_one发出通知

线程B使用wait进行等待

那么线程B将不会被唤醒(即线程B没有察觉到线程A发出的通知),这显然不是程序员想要的结果,发生这种情况的根源在于线程B对条件的检查和进入等待的中间是有空档的。wait函数需要锁作为参数正是为了解决这一问题的。

12345678// Thread Bauto pred = []() { return flag;};std::unique_lock lck(mt);while(!pred()) { cv.wait(lck);}

当线程B调用wait的时候会释放传入的锁并同时进入等待,当被唤醒时会重新获得锁,因此只要保证线程A在修改flag的时候是正确加锁的那么就不会发生前面的这种情况。使用wait函数的另一个重载时下面的代码与上面的6~8行是等价的。

1cv.wait(lck, pred);

不仅仅是C++,就博主所知道的语言但凡有条件变量的概念都必须与锁配合使用。以C#、Java为例

C#

123456789101112131415// Thread Alock (obj){ flag = true; System.Threading.Monitor.Pulse(obj);}// Thread Block (obj){ while(!pred()) { System.Threading.Monitor.Wait(obj); }}

Java

123456789101112// Thread Asynchronized(obj) { flag = true; obj.notify();}// Thread Bsynchronized(obj) { while(!pred()) { obj.wait(); }}

C#与C++不同之处在于C#在Pulse或PulseAll的线程必须持有锁,而C++的notify_one和notify_all则无所谓是否持有锁。

相关手记

365bet线上网址 人体润滑油的5种常见用法
365体育投注网站 UGC 数据分析:衡量影响力和用户参与度
365bet线上网址 iphone5锁屏键教程 苹果5s锁屏键
365体育投注网站 我国运载火箭为什么以“长征”命名
365bet线上网址 什麼是兩女一杯

什麼是兩女一杯

07-19 👁️ 8720
365bet投注网 《鬼话怪谈·祥云寺》

《鬼话怪谈·祥云寺》

07-20 👁️ 1664