pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁(于是其它线程可以修改已链接列表),并等待条件 mycond 发生(这样当 pthread_cond_wait() 接收到另一个线程的“信号”时,它将苏醒)。现在互斥对象已被解锁,其它线程可以访问和修改已链接列表,可能还会添加项。 【要求解锁并阻塞是一个原子操作】
此时,pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生,但等待条件 mycond 通常是一个阻塞操作,这意味着线程将睡眠,在它苏醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将一直睡眠,直到特定条件发生,在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看,它只是在等待 pthread_cond_wait() 调用返回。
现在继续说明,假设另一个线程(称作“2 号线程”)锁定了 mymutex 并对已链接列表添加了一项。在对互斥对象解锁之后,2 号线程会立即调用函数 pthread_cond_broadcast(&mycond)。此操作之后,2 号线程将使所有等待 mycond 条件变量的线程立即苏醒。这意味着第一个线程(仍处于 pthread_cond_wait() 调用中)现在将苏醒。
现在,看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&mymutex) 之后,1 号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() 将执行最后一个操作:重新锁定 mymutex。一旦 pthread_cond_wait() 锁定了互斥对象,那么它将返回并允许 1 号线程继续执行。那时,它可以马上检查列表,查看它所感兴趣的更改。
1: #include
2: #include "error.c"
3:
4: struct msg {
5: struct msg *m_next;
6: /* ... more stuff here ... */
7: };
8:
9: struct msg *workq;
10: pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
11: pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
12:
13: void
14: process_msg(void)
15: {
16: struct msg *mp;
17:
18: for (;;) {
19: pthread_mutex_lock(&qlock);
20: while (workq == NULL)
21: pthread_cond_wait(&qready, &qlock); /*block-->unlock-->wait() return-->lock*/
22: mp = workq;
23: workq = mp->m_next;
24: pthread_mutex_unlock(&qlock);
25: /* now process the message mp */
26: }
27: }
28:
29: void
30: enqueue_msg(struct msg *mp)
31: {
32: pthread_mutex_lock(&qlock);
33: mp->m_next = workq;
34: workq = mp;
35: pthread_mutex_unlock(&qlock);
36: pthread_cond_signal(&qready);
37: }
38:
上面的例子中,遵循了如下步骤:
1:对互斥量加锁(pthread_mutex_lock)。
2:改变互斥量保护的条件。
3:对互斥量解锁(pthread_mutex_unlock)。
4:向等待条件的线程发送信号(pthread_cond_broadcast)。
在上面的步骤中,第3步和第4步之间获取互斥锁,然后使条件失效,最后释放互斥锁;接着,当调用pthread_cond_broadcast时,条件不再为真,线程无需运行。这就是为什么唤醒线程必须重新检查条件,不能仅仅因为pthread_cond_wait返回就假定条件为真。
没有评论:
发表评论