134 / 163 · C11 · 约 8 分钟
条件变量
先记住这句话
条件变量让线程能够高效地休眠直到某个共享条件变为真,从而避免无意义的自旋。它必须与互斥锁配合,通过wait原子释放锁并睡眠、signal唤醒等待者,并依靠显式状态变量防止信号丢失。
锁无法解决的等待问题
互斥锁只能保证临界区互斥,却无法让线程在条件尚未成立时真正休息。若线程反复检查一个标志,处理器会被白白占用。我们需要一种原语,使线程进入睡眠队列,直到别的线程改变状态后再被唤醒。
wait与signal的语义
调用wait时必须已经持有配套的互斥锁。该调用原子地释放锁并把调用者放入条件变量的等待队列;被唤醒后它会重新获得同一把锁才返回。signal则从队列中选出一个线程并使其可运行。两者都必须围绕一个记录真实条件的状态变量来使用。
安全使用的三条铁律
第一,永远在持有锁的情况下执行wait和signal。第二,用while而不是if重新测试条件,以抵御虚假唤醒。第三,必须维护独立的状态变量,否则过早发出的signal会被丢弃,导致等待者永久睡眠。
常见误区
- 未持锁就调用wait会立刻产生未定义行为
- 用if代替while检查条件会因虚假唤醒而出错
- 缺少状态变量时,子线程先结束发出的signal会被忽略
运行一个例子
最低标准 C11 · 完整程序 · 下载 .c
#include <stdio.h>
#include <pthread.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
static int finished = 0;
static void *child_thread(void *arg) { (void)arg;
(void)arg;
printf("child running\n");
pthread_mutex_lock(&mtx);
finished = 1;
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);
return NULL;
}
int main(void) {
printf("parent starting\n");
pthread_t tid;
pthread_create(&tid, NULL, child_thread, NULL);
pthread_mutex_lock(&mtx);
while (finished == 0) {
pthread_cond_wait(&cv, &mtx);
}
pthread_mutex_unlock(&mtx);
printf("parent finishing\n");
pthread_join(tid, NULL);
return 0;
}
在本地编译
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -pthread ostep-30-condition-variables.c -o example && ./example预期结果
parent starting
child running
parent finishing
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
父线程在子线程已经signal之后才调用wait,且代码里没有done之类的状态变量,程序会怎样?
查看参考答案
signal被丢弃,父线程随后进入wait后永远不会被唤醒,程序挂起。
继续查证
标准草案与官方章节会更新;版本标记只说明示例最低要求。