pthread_cond_wait详解

原文链接:http://blog.sina.com.cn/s/blog_77d329940102wvrh.html

通常,和pthread _cond_wait 配对使用的有pthread_cond_signal , 同时还有用于pthread_cond_t初始化的pthread_cond_init,销毁的pthread_cond_destroy函数,还有用于加锁保护的pthread_mutex_lock和pthread_mutex_unlock,稍后会对为什么进行加锁做解释。

     初始化条件变量int pthread_cond_init(pthread_cond_t *cv, pthread_cond_attr *cattr); 

     函数返回值:返回0表示成功,其他都表示失败。对于函数的参数:pthread_cond_attr 是用来设置pthread_cond_t的属性,当传入的值是NULL的时候表示使用默认的属性。这个函数返回时,创建的条件变量保存在cv所指向的内存中。可以用宏PTHREAD_COND_INITIALIZER来初始化条件变量。但是请记住不能用多个线程初始化同一个条件变量,当一个线程要使用条件变量的时候确保它是未被使用的。

    条件变量的销毁:int pthread_cond_destroy(pthread_cond_t *cv); 返回0表示成功,返回其他值都表示失败。

    条件变量的使用: int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex)以及 int pthread_cond_signal(pthread_cond_t *cv);

    使用pthread_cond_wait方式如下:

    pthread _mutex_lock(&mutex)

    while或if(线程执行的条件是否成立)

          pthread_cond_wait(&cond, &mutex);

    线程执行

    pthread_mutex_unlock(&mutex);

   

    需要解释的有两点,为什么要加锁,以及为什么可以使用while和if。首先解释第一点,有两个方面,线程在执行的部分访问的是进程的资源,有可能有多个线程需要访问它,为了避免由于线程并发执行所引起的资源竞争,所以要让每个线程互斥的访问公有资源,但是细心一下就会发现,如果while或者if判断的时候,不满足线程的执行条件,那么线程便会调用pthread_cond_wait阻塞自己,但是它持有的锁怎么办呢,如果他不归还操作系统,那么其他线程将会无法访问公有资源。这就要追究一下pthread_cond_wait的内部实现机制,当pthread_cond_wait被调用线程阻塞的时候,pthread_cond_wait会自动释放互斥锁。释放互斥锁的时机是什么呢:是线程从调用pthread_cond_wait到操作系统把他放在线程等待队列之后,这样做有一个很重要的原因,就是mutex的第二个作用,保护条件。想一想,线程是并发执行的,如果在没有把被阻塞的线程A放在等待队列之前,就释放了互斥锁,这就意味着其他线程比如线程B可以获得互斥锁去访问公有资源,这时候线程A所等待的条件改变了,但是它没有被放在等待队列上,导致A忽略了等待条件被满足的信号。倘若在线程A调用pthread_cond_wait开始,到把A放在等待队列的过程中,都持有互斥锁,其他线程无法得到互斥锁,就不能改变公有资源。这就保证了线程A被放在等待队列上之后才会有公有资源被改变的信号传递给等待队列。对于这点apue给出的解释:The mutex passed to pthread_cond_wait protects the condition.The caller passes it locked to the function, which then atomically places the calling thread on

 the list of threads waiting for the condition and unlocks the mutex. This closes the window between the time that the condition is checked and the time that the

 thread goes to sleep waiting for the condition to change, so that the thread doesn't miss a change in the condition. When pthread_cond_wait returns, 

the mutex is again locked.


  ===============================pthread_cond_wait的补充解释==========================

作者:高大月
链接:https://www.zhihu.com/question/24116967/answer/26766067
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

pthread_cond_wait并不只是自动释放锁、等待、被wakeup后自动获取锁的helper function, 因为释放锁+等待这两步需要是“原子”的。

注意我给“原子”加了引号,因为这里原子的语义并不是真正的原子操作。事实上,这两步根本没法实现成原子操作。设想如果pthread_cond_wait这样实现:
LOCK_ACQUIRE(&mylock);
pthread_mutex_unlock(mutex); // 释放用户传进来的mutex 
wait(); // 把当前线程放到该CV的等待队列上 
LOCK_RELEASE(&mylock);
那么该线程拿着mylock等待了,谁来释放呢?

因此,这里“原子”的意思是,如果线程A调用pthread_cond_wait(cond, mutex)刚释放完mutex,线程B就获得了mutex并且signal或者broadcast了cond,那么需要保证最终的行为跟线程B在线程A wait以后才获得mutex的行为一致,即没有丢失signal,线程A不会一直block。

当然,使用条件变量的时候直接认为pthread_cond_wait是原子操作就行了:)

觉得我没有解释清楚可以看 这里
The  pthread_cond_wait() and  pthread_cond_timedwait() functions are used to block on a condition variable. They are called with  mutex locked by the calling thread or undefined behaviour will result.

These functions atomically release mutex and cause the calling thread to block on the condition variable cond; atomically here means "atomically with respect to access by another thread to the mutex and then the condition variable". That is, if another thread is able to acquire the mutex after the about-to-block thread has released it, then a subsequent call to pthread_cond_signal() orpthread_cond_broadcast() in that thread behaves as if it were issued after the about-to-block thread has blocked.


       若条件变量不和互斥锁配合使用,会导致唤醒信号丢失,导致调用pthread_cond_wait的等待线程不会被唤醒


唤醒丢失问题

在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。

唤醒丢失往往会在下面的情况下发生:

  1. 一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
  2. 另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
  3. 没有线程正在处在阻塞等待的状态下。

   ===============================pthread_cond_wait的补充解释==========================

   接下来讲解使用while和if判断线程执行条件是否成立的区别。一般来说,在多线程资源竞争的时候,在一个使用资源的线程里面(消费者)判断资源是否可用,不可用便调用pthread_cond_wait,在另一个线程里面(生产者)如果判断资源可用的话,则调用pthread_cond_signal发送一个资源可用信号。但是在wait成功之后,资源就一定可以被使用么,答案是否定的,如果同时有两个或者两个以上的线程正在等待此资源,wait返回后,资源可能已经被使用了,在这种情况下,应该使用:

while(resource == FALSE)

      pthread_cond_wait(&cond, &mutex);

如果之后一个消费者,那么使用if就可以了。解释一下原因,分解pthread_cond_wait的动作为以下几步:

1,线程放在等待队列上,解锁

2,等待 pthread_cond_signal或者pthread_cond_broadcast信号之后去竞争锁

3,若竞争到互斥索则加锁。

上面讲到,有可能多个线程在等待这个资源可用的信号,信号发出后只有一个资源可用,但是有A,B两个线程都在等待,B比较速度快,获得互斥锁,然后加锁,消耗资源,然后解锁,之后A获得互斥锁,但他回去发现资源已经被使用了,它便有两个选择,一个是去访问不存在的资源,另一个就是继续等待,那么继续等待下去的条件就是使用while,要不然使用if的话pthread_cond_wait返回后,就会顺序执行下去。

    下面来讲一下:pthread_cond_wait和pthread_cond_singal是怎样配对使用的:

     等待线程:

     pthread_cond_wait前要先加锁
     pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活
     pthread_cond_wait被激活后会再自动加锁

     激活线程:
     加锁(和等待线程用同一个锁)
     pthread_cond_signal发送信号(阶跃信号前最好判断有无等待线程
     解锁
     激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部。

    

[cpp]  view plain  copy
  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. #include  
  6.   
  7. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
  8. pthread_cond_t  cond  = PTHREAD_COND_INITIALIZER;  
  9.   
  10. int count = 0;  
  11.   
  12. void *decrement(void *arg) {  
  13.     printf("in derement.\n");  
  14.     pthread_mutex_lock(&mutex);  
  15.     if (count == 0)  
  16.         pthread_cond_wait(&cond, &mutex);  
  17.     count--;  
  18.     printf("----decrement:%d.\n", count);  
  19.     printf("out decrement.\n");  
  20.     pthread_mutex_unlock(&mutex);  
  21.     return NULL;  
  22. }  
  23.   
  24. void *increment(void *arg) {  
  25.     printf("in increment.\n");  
  26.     pthread_mutex_lock(&mutex);  
  27.     count++;  
  28.     printf("----increment:%d.\n", count);  
  29.     if (count != 0)  
  30.         pthread_cond_signal(&cond);  
  31.     printf("out increment.\n");  
  32.     pthread_mutex_unlock(&mutex);  
  33.     return NULL;  
  34. }  
  35.   
  36. int main(int argc, char *argv[]) {  
  37.     pthread_t tid_in, tid_de;  
  38.     pthread_create(&tid_de, NULL, (void*)decrement, NULL);  
  39.     sleep(2);  
  40.     pthread_create(&tid_in, NULL, (void*)increment, NULL);  
  41.     sleep(5);  
  42.     pthread_join(tid_de, NULL);  
  43.     pthread_join(tid_in, NULL);  
  44.     pthread_mutex_destroy(&mutex);  
  45.     pthread_cond_destroy(&cond);  
  46.     return 0;  
  47. }  
这个例子中, decrement函数中也可以使用while,读者可以换一下试试。

 

然后在给出一个生产者消费者的例子:

 

[cpp]  view plain  copy
  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5.   
  6. typedef struct node_s {  
  7.     int data;  
  8.     struct node_s *next;  
  9. }node_t;  
  10.   
  11. node_t *head = NULL;  
  12.   
  13. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
  14. pthread_cond_t  condition = PTHREAD_COND_INITIALIZER;  
  15.   
  16. void cleanup_handler(void *arg) {  
  17.     printf("cleanup_handler is running.\n");  
  18.     free(arg);  
  19.     pthread_mutex_unlock(&mutex);  
  20. }  
  21.   
  22. void *thread_func(void *arg) {  
  23.     node_t *p = NULL;  
  24.     pthread_cleanup_push(cleanup_handler, p);  
  25.     while (1) {  
  26.         pthread_mutex_lock(&mutex);  
  27.         while (NULL == head)  
  28.             pthread_cond_wait(&condition, &mutex);  
  29.         p = head;  
  30.         head = head->next;  
  31.         printf("process %d node.\n", p->data);  
  32.         free(p);  
  33.         pthread_mutex_unlock(&mutex);  
  34.     }  
  35.     pthread_cleanup_pop(0);  
  36.     return NULL;  
  37. }  
  38.   
  39. int main(int argc, char *argv[]) {  
  40.     pthread_t tid;  
  41.     node_t *temp = NULL;  
  42.     int i;  
  43.     pthread_create(&tid, NULL, (void*)thread_func, NULL);  
  44.     for (i = 0; i < 10; i++) {  
  45.         temp = (node_t*)malloc(sizeof(node_t));  
  46.         temp->data = i;  
  47.         pthread_mutex_lock(&mutex);  
  48.         temp->next = head;  
  49.         head = temp;  
  50.         pthread_cond_signal(&condition);  
  51.         pthread_mutex_unlock(&mutex);  
  52.         sleep(1);  
  53.     }  
  54.     pthread_cancel(tid);  
  55.     pthread_join(tid, NULL);  
  56.     return 0;  
  57. }  
pthread_cond_wait详解

其中的 pthread _cleanup_push  和 pthread_cleanup_pop,pthread_cancel作为下次博客讲解的内容。


===============================man pthread_cond_wait的解释==========================

LINUX环境下多线程编程肯定会遇到需要条件变量的情况,此时必然要使用pthread_cond_wait()函数。但这个函数的执行过程比较难于理解。
    pthread_cond_wait()的工作流程如下(以MAN中的EXAMPLE为例):
       Consider two shared variables x and y, protected by the mutex mut, and a condition vari-
       able cond that is to be signaled whenever x becomes greater than y.

              int x,y;
              pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
              pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

       Waiting until x is greater than y is performed as follows:

              pthread_mutex_lock(&mut);
              while (x <= y) {
                      pthread_cond_wait(&cond, &mut);
              }
             
              pthread_mutex_unlock(&mut);

       Modifications on x and y that may cause x to become greater than y should signal the con-
       dition if needed:

              pthread_mutex_lock(&mut);
             
              if (x > y) pthread_cond_broadcast(&cond);
              pthread_mutex_unlock(&mut);

     这个例子的意思是,两个线程要修改X和 Y的值,第一个线程当X<=Y时就挂起,直到X>Y时才继续执行(由第二个线程可能会修改X,Y的值,当X>Y时唤醒第一个线程),即 首先初始化一个普通互斥量mut和一个条件变量cond。之后分别在两个线程中分别执行如下函数体:

                pthread_mutex_lock(&mut);
              while (x <= y) {
                      pthread_cond_wait(&cond, &mut);
              }
             
              pthread_mutex_unlock(&mut);

和:       pthread_mutex_lock(&mut);
             
              if (x > y) pthread_cond_signal(&cond);
              pthread_mutex_unlock(&mut);
 
    其实函数的执行过程非常简单,在第一个线程执行到pthread_cond_wait(&cond,&mut)时,此时如果X<=Y,则此函数就将mut互斥量解锁 ,再将cond条件变量加锁 ,此时第一个线程挂起 (不占用任何CPU周期)。
    而在第二个线程中,本来因为mut被第一个线程锁住而阻塞,此时因为mut已经释放,所以可以获得锁mut,并且进行修改X和Y的值,在修改之后,一个IF语句判定是不是X>Y,如果是,则此时pthread_cond_signal()函数会唤醒第一个线程 ,并在下一句中释放互斥量mut。然后第一个线程开始从pthread_cond_wait()执行,首先要再次锁mut , 如果锁成功,再进行条件的判断 (至于为什么用WHILE,即在被唤醒之后还要再判断,后面有原因分析),如果满足条件,则被唤醒 进行处理,最后释放互斥量mut 。

    至于为什么在被唤醒之后还要再次进行条件判断(即为什么要使用while循环来判断条件),是因为可能有“惊群效应”。有人觉得此处既然是被唤醒的,肯定 是满足条件了,其实不然。如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时就必须要再次条件判断,以使只有一个线程进入临界区处理。 对此,转来一段:

引用下POSIX的RATIONALE: 

Condition Wait Semantics 

It is important to note that when pthread_cond_wait() and pthread_cond_timedwait() return without error, the associated predicate may still be false. Similarly, when pthread_cond_timedwait() returns with the timeout error, the associated predicate may be true due to an unavoidable race between the expiration of the timeout and the predicate state change. 

The application needs to recheck the predicate on any return because it cannot be sure there is another thread waiting on the thread to handle the signal, and if there is not then the signal is lost. The burden is on the application to check the predicate. 

Some implementations, particularly on a multi-processor, may sometimes cause multiple threads to wake up when the condition variable is signaled simultaneously on different processors. 

In general, whenever a condition wait returns, the thread has to re-evaluate the predicate associated with the condition wait to determine whether it can safely proceed, should wait again, or should declare a timeout. A return from the wait does not imply that the associated predicate is either true or false. 

It is thus recommended that a condition wait be enclosed in the equivalent of a "while loop" that checks the predicate. 

从上文可以看出: 
1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上 的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 
2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环.

       其实说白了很简单,就是pthread_cond_signal()也可能唤醒多个线程,而如果你同时只允许一个线程访问的话,就必须要使用while来进行条件判断,以保证临界区内只有一个线程在处理。

 

pthread_cond_wait()   用于阻塞当前线程,等待别的线程使用  pthread_cond_signal()  pthread_cond_broadcast来唤醒它     pthread_cond_wait()     必须与pthread_mutex 配套使用。 pthread_cond_wait()  函数一进入wait状态就会自动release mutex。当其他线程通过  pthread_cond_signal()  pthread_cond_broadcast  ,把该线程唤醒,使  pthread_cond_wait()通过(返回)时,该线程又自动获得该 mutex 
        pthread_cond_signal  函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
        使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。
        但是  pthread_cond_signal  在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,

 

==============================另一篇很好的文章===========================

POSIX线程详解

==============================使用效率问题============================

 

pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
但使用pthread_cond_signal不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。
另外,互斥量的作用一般是用于对某个资源进行互斥性的存取,很多时候是用来保证操作是一个原子性的操作,是不可中断的。
用法:
pthread_cond_wait必须放在pthread_mutex_lock和pthread_mutex_unlock之间,因为他要根据共享变量的状态来决定是否要等待,而为了不永远等待下去所以必须要在lock/unlock队中
共享变量的状态改变必须遵守lock/unlock的规则
pthread_cond_signal即可以放在pthread_mutex_lock和pthread_mutex_unlock之间,也可以放在pthread_mutex_lock和pthread_mutex_unlock之后,但是各有各缺点。
之间:
pthread_mutex_lock
xxxxxxx
pthread_cond_signal
pthread_mutex_unlock
缺点:在某下线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal)然后又回到内核空间(因为cond_wait返回后会有原子加锁的行为),所以一来一回会有性能的问题。
在code review中,我会发现很多人喜欢在pthread_mutex_lock()和pthread_mutex_unlock(()之间调用 pthread_cond_signal或者pthread_cond_broadcast函数,从逻辑上来说,这种使用方法是完全正确的。但是在多线程环境中,这种使用方法可能是低效的。posix1标准说,pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是说,可以在lock与unlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。这里举个例子:
       
我们假设系统中有线程1和线程2,他们都想获取mutex后处理共享数据,再释放mutex。请看这种序列:
       
1)线程1获取mutex,在进行数据处理的时候,线程2也想获取mutex,但是此时被线程1所占用,线程2进入休眠,等待mutex被释放。
       
2)线程1做完数据处理后,调用pthread_cond_signal()唤醒等待队列中某个线程,在本例中也就是线程2。线程1在调用pthread_mutex_unlock()前,因为系统调度的原因,线程2获取使用CPU的权利,那么它就想要开始处理数据,但是在开始处理之前,mutex必须被获取,很遗憾,线程1正在使用mutex,所以线程2被迫再次进入休眠。
       
3)然后就是线程1执行pthread_mutex_unlock()后,线程2方能被再次唤醒。
       从这里看,使用的效率是比较低的,如果再多线程环境中,这种情况频繁发生的话,是一件比较痛苦的事情。
但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
所以在Linux中推荐使用这种模式。
之后:
pthread_mutex_lock
xxxxxxx
pthread_mutex_unlock
pthread_cond_signal
优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了
缺点:如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程),而这在上面的放中间的模式下是不会出现的。
所以,在Linux下最好pthread_cond_signal放中间,但从编程规则上说,其他两种都可以

  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值