条件变量是一个用于存放等待线程的队列,使线程在条件不满足时进入队列睡眠,并由其他线程在条件改变后从队列中唤醒它们的同步机制。
API
pthread_cond_wait(pthread_cond_t*, pthread_mutex_t*)pthread_cond_signal(pthread_cond_t*)pthread_cond_broadcast()pthread_cond_destroy(pthread_cond_t*)
使用常规
使用条件变量一定要搭配一个状态变量 (state variable) 与一个互斥锁
- 状态变量:条件变量本身是一个「等待队列」,其不存储关心的条件是否满足的信息;因此需要一个显式的状态变量(通常是布尔值,计数器等)来记录。
- 互斥锁:条件变量的睡眠与唤醒操作必须是原子的,且多进程共享的状态变量也需要保护。
线程被 pthread_cond_wait() 阻塞时释放互斥锁。
生产者/消费者问题
又叫有界缓冲区 (bounded buffer) 问题,由 Dijkstra 提出(这里也能见到你!)。
生产与消费均为 1 单位。
1 | void *producer(void *arg) { |
1 | void *consumer(void *arg) { |
- 状态变量
count - 双条件变量
empty:确定当前缓冲区未满,从队列中唤醒一名生产者进行生产fill:确定当前缓冲区非空,从队列唤醒一名消费者进行消费
- 使用
while而非if:避免被唤醒与修改缓冲区间的中断产生影响
广播
生产者/消费者问题也可以(丑陋地)使用单条件变量解决。
如果仅仅把代码中的双条件变量改为单个是不够的。考虑一个单生产者双消费者的例子:
- 生产者生产 1 单位,唤醒消费者 A,睡眠
- 消费者 A 消费 1 单位,唤醒消费者 B,睡眠
- 缓冲区为空,消费者 B 未通过
while条件检查,睡眠 - 此时所有线程都陷入睡眠,死锁
可以发现单条件变量的问题在于无法唤醒需要被唤醒的线程。
Lampson 与 Redell 的方案 条件覆盖 (Covering Conditions):把所有线程都唤醒不就好了?那些不需要被唤醒的线程将因为无法通过 while 条件检查继续睡眠。
- 问题解决:把所有
Pthread_cond_signal()换成Pthread_cond_broadcast()即可。