等待条件变量的正确姿势:
void wait() { mutex.lock() while (wait_flag == false) { condition.wait() } do_something(); mutex.unlock(); }
1)必须使用while循环来等待条件变为真,即醒来之后要立马再判断一次条件是否成立再决定是否需要继续等待,
因为很有可能条件并不为真,但是线程却被各种奇怪的中断或者pthread_cond_broadcast这样的东西给唤醒了
2)至于condition.wait()的作用是提供一个原子操作:
进入condition.wait() 时,将 wait 和 mutex.unlock 两步变成一个原子操作,确保在线程进入 wait 之前不会释放锁,也就是保证了在进入 wait 之前没有人能够改动 wait_flag (所有对 wait_flag的操作都要加锁,否则条件变量没有意义),
因为pthread_cond_signal这类函数只会唤醒已经在等待队列里的线程,如果这两步不是原子操作,那么在释放锁之后,进入等待队列之前的这段时间里,有人调用了cond_signal之后也不会被唤醒
触发条件的正确姿势:
void notify() { mutex.lock(); push_resource(); condition.signal(); mutex.unlock(); } void notifyAll() { mutex.lock(); change_status(); condition.broadcast(); mutex.unlock(); }
通常在资源可用时使用signal,在改变状态时使用broadcast。
这里特别说明一下网上流传的阻塞队列 (为简单起见,这里假设队列最大长度为无限长) 的实现有个错误:
void push(e) { mutex.lock(); queue.push(e); if (queue.size() == 1) { //错误,应该把if条件去掉 cond.notify(); //或者不去掉if,改成notifyAll } mutex.unlock(); }
因为pthread_cond_signal只是唤醒线程到就绪队列,至于这个线程能不能抢到锁以及抢到锁之后能不能保证立马被调度并从队列里取元素就不保证了。
所以会出现这种情况,有多个饥饿的线程在等待队列变成非空,这个时候有一个线程放进去一个元素,之后唤醒了一个等待线程到就绪队列,但是他却没有获取到锁,
这个时候其他的线程往队列里push的时候,由于消费者线程没有来得及取元素,所以队列元素的总量大于1,所以并不触发signal。。。
这就导致在下次队列变为空之前,可能只有一个消费者线程会被唤醒。
使用notifyAll可以避免这个问题,但是代价是虚假唤醒(据说对这种情况会优化,这里不讨论。。恩,因为我特么也是不懂啊,记住正确的做法不就好了么)
所以对于有多个消费者线程的阻塞队列,正确的写法是每次push都进行一次notify(),而不是队列为空时才notify