We’re going to make you an offer you can’t refuse.
简介
在多线程并发的程序中,我们有互斥锁的机制保证线程同步,但在有些情况下,仅仅依靠互斥锁是不够的,我们有时还会遇到下面的情况:令一个线程等待,直到特殊情况发生为止。例如,我们希望父线程在继续执行前检查子线程是否完成(join函数),那么我们如何实现这个功能呢?一个简单的例子是使用一个volatile的全局变量。
1 | volatile int done = 0; |
条件变量
条件变量定义
条件变量是多线程程序中用来实现“等待—>唤醒”机制的手段,以两个线程为例,通常我们会执行如下动作:
- 线程1等待条件变量指定的条件成立,如果不成立,则挂起
- 线程2使条件成立,从而使线程1从挂起状态转移至执行状态
通常,条件变量指定的条件是一个全局变量,这样两个线程都可以进行访问,但是我们必须采用互斥锁对变量上锁,避免发生访问冲突。
条件变量应用场景
- 生产者消费者问题
- 读写问题中用于切换读写状态
条件变量使用注意
虚假唤醒(spurious wakeup)
虚假唤醒会影响程序的性能,有几种情况可能导致不满足条件的情况下,线程被唤醒:
- 一个signal唤醒了多个线程,这种情况详见:pthread_cond_signal(3) - Linux man page
The pthread_cond_broadcast() function shall unblock all threads currently blocked on the specified condition variable cond.
没有signal但是线程就是被唤醒了
线程被唤醒,但是得不到锁,只能继续等待
1 | // Writer |
在上面这个代码中,Writer广播了条件,一个reader接收到条件被唤醒,但是如果Writer此时没有释放锁,那么Reader只能重新等待。一个解决方法是将Broadcast和Signal提到外边,但是这个只在本例中适用。
使用注意
- 不要忘记提醒等待的线程
- 如果不确定应该单播还是广播,就使用广播,虽然会影响性能
C++11中的条件变量
创建条件变量
C++11中的条件变量创建方法如下:
1 | std::condition_variable cond;//条件变量 |
其中,condition_variable是一个数据结构,保存了锁、等待的线程等关键信息
wait方法
wait会使当前线程阻塞,直到达到某种特定的条件将线程唤醒。其通用的API定义为:
1 | wait(mutex, cond) |
使用wait方法时,我们必须保证调用wait的线程释放了mutex,然后等wait被唤醒后又自动重新上锁。wait方法的逻辑如下:
1 | wait(mutex, cond){ |
C++ wait实现
在C++11中,其函数定义如下:
1 | template< class Predicate > |
该函数包含两个参数,含义如下:
- lock - 一个
std::unique_lock<std::mutex>
对象,使用前必须上锁 - pred - 一个返回bool类型的函数,如果返回
false
,代表线程需要继续挂起,返回true代表可以执行
wait(两参数)的实现如下
1 | template<typename _Predicate> |
可以看到wait
函数逻辑很简单,如果条件不满足,就一直等待。而一个参数的wait
实际是调用了pthread_cond_wait
,这个函数的详细描述见参考文献1
signal方法
唤醒一个/所有正在等待某个条件的线程,其通用API定义如下
1 | signal(cond) //唤醒一个特定的线程 |
在C++中,signal方法定义为notify_one()
和notify_all()
条件变量使用
条件变量和锁控制临界区框架
这里提供一个典型的使用条件变量和锁对临界区进行控制的框架
1 | Lock(mutex) |
C++多线程交替打印
1 |
|