前言:发出消息的线程叫做检测线程,响应消息的线程称为响应线程。
C++11线程间的通信方式有三种:
(1)通过条件变量进行线程间的通信
(2)通过标志位来通知线程间的通信
(3)通过std::furture来进行线程间的通信
- (1)通过条件变量进行线程间的通信
全局区间中定义如下:
std::condition_variable cv;// condvar for event
std::mutex m;//mutex for use with cv
检测线程中代码如下:
cv.notify_one();//如果有多个响应线程则使用notify_all代替notify_one.
响应线程中代码如下:
std::unique_lock<std::mutex> lk(m)//lock mutex
cv.wait(lk);//这是有缺陷的语句
缺陷:
- 如果检测线程在响应线程等待之前通知condvar,那么响应线程会永远的进行挂起,也就是说:如果检测线程在响应线程执行等待之前就已经执行了通知,那么响应线程就会错过通知,并永远的等待下去。
- 条件变量的wait无法避免伪唤醒。(伪唤醒:线程API的等待即使没有通知condvar,等待条件变量的代码也有可能被唤醒)一般使用lambda来避免伪唤醒的情况。
cv.wait(lk,[]{return whether the event has occurred;});//利用这种能力来要求响应线程能够确定它等待的条件是否为真。
- (2)使用共享布尔标志来进行线程间的通信
全局区间中的定义如下:
std::atomic<bool> flag(false); //shared flag
…
flag = true;//tall reacting thread
响应线程只是简单的轮询标志,当它看到标志为true时,它知道等待的事件已经发生。
white(!flag);//wait for event
这种方法没有互斥变量也没有伪唤醒。不足之处就是响应线程中的轮询的成本。(也就是说占用了硬件线程)
- (3)综上两种的结合
我们通常将condvar和基于标志的设计组合使用。标志指示感兴趣的事件是否已经发生,而对标志的访问是由互斥锁同步的,由于互斥阻止对标志的并发访问,不需要将标志声明为std::atomic,一个简单的bool就可以。
全局区间:
Std::conditional_variable cv;
Std::mutex m;
检测线程:
Bool flag(false);//not atomic
{
std::lock_guard<std::mutex> lg;
Flag = true;
}
cv.notify_one;//tell reactive thread
响应线程:
std::unique_lock<std::mutex> lk(m);
cv.wait(lk,[]{return flag;};//use lambda to avoid 伪唤醒
这种方法避免了上述所有的缺点,但是依然比较笨拙。
- (4)让响应线程wait由检测线程设置的future,从而避免使用条件变量、互斥锁和标志
future表征的是从被调用者到(异步)调用者的通信通道的接收端,在这里,检测和响应线程之间没有被调用者与调用者关系。发送端为std::promise且接受端为future的通信信道不仅可跨线程,还易用用与任何需要将信息从程序中的一个位置传输到另一个位置的情况。例如:
在全局作用域下:
Std::promise<void> p;//promise for communication channel
检测线程的作用域下:
p.set_value();//tell reactinve task
响应线程的作用域下:
p.get_future().wait();//wat on future corresponding to p
优点:不需要互斥锁,不管检测线程是否在响应任务等待之前设置了std::promise,它都能正常工作,不受伪唤醒影响(只有条件变量才有伪唤醒),等待时不消耗硬件系统资源。
缺点:std::promise只能设置一次,std::promise和future之间的通信通道是一个一次性的机制。