Thread Condition Signal 的两个陷阱
Thread Condition Signal
当接触 线程条件信号 时,通常是实现生产者和消费者的场景。翻看 man 手册后,很疑惑为什么 cond 需要依赖外部的 mutex?
在 man 手册中没有 example 可以参考,很容易不假思索的写成下面这样子有陷阱的代码:
// producer
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
// consumer
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
这样子写会步入 信号丢失的陷阱 中。
信号丢失的陷阱
当 signal 发生于 wait 之前,信号就会丢失
+ +----------+ +----------+
| | producer | | consumer |
| +----------+ +----------+
|
| +----------+
| | lock |
| +----------+
| +----------+
| | signal |
| +----------+
Time | +----------+
| | unlock |
| +----------+
|-------------------------------
| +----------+
| | lock |
| +----------+
| +----------+
| | wait |
| +----------+
| +----------+
| | unlock |
v +----------+
这里是一个生产者,一个消费者的场景。 producer 优先执行,导致了信号丢失,consumer 一直在 wait 中。
虚假唤醒的陷阱
man pthread_cond_broadcast
文档中,Multiple Awakenings by Condition Signal
段落提到的 spurious wakeup
问题。
考虑到一个生产者,多个消费者的场景:
一个线程尝试等待条件变量,另一个线程并发执行到了 pthread_cond_signal
,第三个线程已经在等待中。
如下伪代码实现与执行步骤(末尾数字):
pthread_cond_wait(mutex, cond):
value = cond->value; /* 1 */
pthread_mutex_unlock(mutex); /* 2 */
pthread_mutex_lock(cond->mutex); /* 10 */
if (value == cond->value) { /* 11 */
me->next_cond = cond->waiter;
cond->waiter = me;
pthread_mutex_unlock(cond->mutex);
unable_to_run(me);
} else
pthread_mutex_unlock(cond->mutex); /* 12 */
pthread_mutex_lock(mutex); /* 13 */
pthread_cond_signal(cond):
pthread_mutex_lock(cond->mutex); /* 3 */
cond->value++; /* 4 */
if (cond->waiter) { /* 5 */
sleeper = cond->waiter; /* 6 */
cond->waiter = sleeper->next_cond; /* 7 */
able_to_run(sleeper); /* 8 */
}
pthread_mutex_unlock(cond->mutex); /* 9 */
调用一次 pthread_cond_signal
,导致了多个 consumer 线程在 pthread_cond_wait
或者 pthread_cond_timedwait
返回,这现象称为 spurious wakeup
。
解决方法
当实现 Thread Condition Signal 逻辑时,外部的 mutex 锁是为了保证正确性,加入一个条件变量以保证唤醒信号不会丢失。
如下正确的写法:
// producer
pthread_mutex_lock(&mutex);
condition_ = true;
pthread_cond_signal(&cond, &mutex);
pthread_mutex_unlock(&mutex);
// consumer
pthread_mutex_lock(&mutex);
while (!condition_) {
pthread_cond_wait(&cond);
}
condition_ = false;
pthread_mutex_unlock(&mutex);
如果是多个生产者多个消费者的情况,可以将条件改成 count 计数器。
参考
https://man7.org/linux/man-pages/man3/pthread_cond_broadcast.3p.html
https://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html