条件等待与通知
条件等待原语用于在线程之间传递“状态已经满足”的信号。它总是与互斥量配合使用:互斥量保护状态检查,条件对象负责阻塞与唤醒。
下文示例同样基于 <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
<输出与输入或平台相关,请以实际运行为准>
2. signal 与 broadcast
cnd_signal 唤醒一个等待线程,cnd_broadcast 唤醒全部等待线程。若条件满足后可能有多个线程都应继续执行,使用 broadcast 更合适。
3. 实践建议
把“状态对象、互斥量、条件对象”封装在同一个模块中,避免外部直接操纵内部同步细节。同步原语本身并不复杂,复杂的是状态协议;协议集中,问题就更容易定位。
4. wait 的锁语义
cnd_wait 的关键语义是“原子地释放互斥量并进入等待,被唤醒后再重新获取互斥量返回”。这保证了等待方不会错过状态变化与通知之间的窗口。也正因为返回时锁已重新持有,等待方应先更新或读取受保护状态,再决定是继续等待还是退出循环。
5. 状态优先于通知
条件对象传递的是“可能可继续”的信号,而不是状态本身。真正的判断依据始终是受互斥量保护的状态对象,所以等待条件必须写成循环判定。只要把“先改状态、再通知;被唤醒后先查状态”这条顺序保持一致,条件等待模型就会稳定很多。