23.4 条件等待与通知
条件等待原语用于在线程之间传递“状态已经满足”的信号。它总是与互斥量配合使用:互斥量保护状态检查,条件对象负责阻塞与唤醒。
下文示例同样基于 <threads.h>;若你的实现缺少该接口,可按相同状态协议改写到平台并发库。
1. 典型模式
等待方必须在循环中检查条件,而不是只用一次 if。这是为了处理虚假唤醒,以及多个线程竞争同一状态时的重新判定。
c
#include <stdio.h>
#include <threads.h>
static mtx_t lock;
static cnd_t cond;
static int ready = 0;
int producer(void *arg) {
(void)arg;
mtx_lock(&lock);
ready = 1;
cnd_signal(&cond);
mtx_unlock(&lock);
return 0;
}
int consumer(void *arg) {
(void)arg;
mtx_lock(&lock);
while (!ready) {
cnd_wait(&cond, &lock);
}
puts("consumer: ready");
mtx_unlock(&lock);
return 0;
}
int main(void) {
thrd_t t1, t2;
mtx_init(&lock, mtx_plain);
cnd_init(&cond);
thrd_create(&t1, consumer, NULL);
thrd_create(&t2, producer, NULL);
thrd_join(t1, NULL);
thrd_join(t2, NULL);
cnd_destroy(&cond);
mtx_destroy(&lock);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
线程与同步对象均创建成功时,输出为:
bash
consumer: ready
2. signal 与 broadcast
cnd_signal 唤醒一个等待线程,cnd_broadcast 唤醒全部等待线程。若条件满足后可能有多个线程都应继续执行,使用 broadcast 更合适。
3. wait 的锁语义
cnd_wait 的关键语义是“原子地释放互斥量并进入等待,被唤醒后再重新获取互斥量返回”。这保证了等待方不会错过状态变化与通知之间的窗口。也正因为返回时锁已重新持有,等待方应先更新或读取受保护状态,再决定是继续等待还是退出循环。
4. 状态优先于通知
条件对象传递的是“可能可继续”的信号,而不是状态本身。真正的判断依据始终是受互斥量保护的状态对象,所以等待条件必须写成循环判定。只要把“先改状态、再通知;被唤醒后先查状态”这条顺序保持一致,条件等待模型就会稳定很多。