聊聊pthread_cond_wait的虚假唤醒

使用条件变量时,仅仅从pthread_cond_wait返回就说条件成立是不恰当的。我们正确使用pthread_cond_wait的唯一方式是当线程被从pthread_cond_wait唤醒时,再检查一下我们等待的条件(pthread_cond_wait的返回使得我们再次获得mutex,这时我们可以讲,我们拥有绝对的对等待条件的访问权,我们对其状态拥有绝对自信)

我看到有人把POSIX对pthread_cond_signal实现的约束(详见apue page334)作为从pthread_cond_wait返回必须进行变量值检查的原因,这不够准确,根本原因只有一个,只有当前线程获取到了被保护变量的mutex才能对被保护变量的值做推论。

为了更好的说明问题,假设有这样一个应用场景:我们使用线程A,B来消费一个值,线程C用于生产这个值,生产后就广播A与B,主线程等待他们。

 1 #include<stdio.h>
 2 #include<pthread.h>
 3 #include<stdlib.h>
 4 #include<unistd.h>
 5 #include<errno.h>
 6 #include<assert.h>
 7 #include<signal.h>
 8 
 9 
10 pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
11 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
12 
13 volatile int value = 0;
14 
15 pthread_t tid;
16 pthread_t tid2;
17 pthread_t tid3;
18 
19 void* thread_func(void* arg) {
20     pthread_mutex_lock(&mutex);
21     
22     if (0 == value) {
23         pthread_cond_wait(&cond, &mutex);
24     }
25     printf("the thread_func is finished value = %d\n", value);
26     value = 0;
27     pthread_mutex_unlock(&mutex);
28 }
29 
30 void* thread_func3(void* arg) {
31     pthread_mutex_lock(&mutex);
32     
33     if (0 == value) {
34         pthread_cond_wait(&cond, &mutex);
35     }
36     printf("the thread_func3 is finished value = %d\n", value);
37     value = 0;
38     pthread_mutex_unlock(&mutex);
39 }
40 
41 void* thread_func2(void* arg) {
42     pthread_mutex_lock(&mutex);
43     value = 1;
44     pthread_cond_broadcast(&cond);
45     printf("the thread_func2 is finished value = %d\n", value);
46     pthread_mutex_unlock(&mutex);
47 }
48 
49 int
50 main(int argc, char* argv[])
51 {
52 //    pthread_attr_t attr;
53 //    assert(0 == pthread_attr_init(&attr));
54 //    pthread_create(&tid2, NULL, thread_func2, NULL);
55 //    pthread_join(tid2, NULL);
56 //    printf("thread id = %ld\n", tid);
57 //    printf("thread2 id = %ld\n", tid2);
58 //    printf("thread3 id = %ld\n", tid3);
59 //    printf("---------keep going---------\n");
60 //    pthread_create(&tid, NULL, thread_func, NULL);
61 //    pthread_create(&tid3, NULL, thread_func3, NULL);
62 //    pthread_join(tid, NULL);
63 //    pthread_join(tid3, NULL);
64 //    pthread_cond_destroy(&cond);
65 
66     pthread_attr_t attr;
67     assert(0 == pthread_attr_init(&attr));
68     pthread_create(&tid, NULL, thread_func, NULL);
69     pthread_create(&tid3, NULL, thread_func3, NULL);
70     sleep(5);
71     printf("thread id = %ld\n", tid);
72     printf("thread3 id = %ld\n", tid3);
73     printf("make sure the thread and thread3 is created\n");
74 
75     pthread_create(&tid2, NULL, thread_func2, NULL);
76     pthread_join(tid2, NULL);
77     pthread_join(tid, NULL);
78     pthread_join(tid3, NULL);
79     pthread_cond_destroy(&cond);
80     pthread_attr_destroy(&attr);
81 
82 }

程序中需要注意line70,为了使得pthread_cond_broadcast成功,这里被动等待了5s,其它的都没啥说的。

我们假设thread_func收到广播后先执行,thread_func持有mutex,所以thread_func3在从pthread_cond_wait这里返回就是失败的,虽然收到了广播,但是它压根拿不到mutex。

等到thread_fucnc释放mutex后,thread_func3开始执行,这时value的值已经不是thread_func3被通知时的值了(这个例子比较极端)。所以我们在从pthread_cond_wait返回后需要再验证我们等待的value是否是期望的值。

那我们再进一步,thread_func3从pthread_cond_wait返回后再加个if判断不就行了吗???代码如下:

...
//in the thread_func3
if(0 == value) {
    pthread_cond_wait();    
}
if(0 == value) {
    pthread_cond_wait();
}
...

那如果又发生了thread_func抢先消费掉value的情况呢?是不是又需要判断一次value,如此往复,当然写成这样才是唯一解了:

while(0 == value) {
     pthread_cond_wait();   
}

好了,谢谢各位的阅读。恳请批评指正!

posted @ 2017-11-01 10:18  jianqiao_shi  阅读(1244)  评论(0编辑  收藏  举报