• 多线程编程中条件变量和的spurious wakeup 虚假唤醒


    1. 概述 
    条件变量(condition variable)是利用共享的变量进行线程之间同步的一种机制。典型的场景包括生产者-消费者模型,线程池实现等。 
    对条件变量的使用包括两个动作: 
    1) 线程等待某个条件, 条件为真则继续执行,条件为假则将自己挂起(避免busy wait,节省CPU资源); 
    2) 线程执行某些处理之后,条件成立;则通知等待该条件的线程继续执行。 
    3) 为了防止race-condition,条件变量总是和互斥锁变量mutex结合在一起使用。 
    一般的编程模式: 

    C++代码  收藏代码
    1. var mutex;  
    2. var cond;  
    3. var something;  
    4.   
    5. Thread1: (等待线程)  
    6. lock(mutex);  
    7. while( something not true ){  
    8.     condition_wait( cond, mutex);  
    9. }  
    10. do(something);  
    11. unlock(mutex);  
    12.   
    13. //============================  
    14.   
    15. Thread2: (解锁线程)  
    16.   
    17. do(something);  
    18. ....  
    19. something = true;  
    20.   
    21. unlock(mutex);  
    22. condition_signal(cond);  


    函数说明: 
    (1) Condition_wait():调用时当前线程立即进入睡眠状态,同时互斥变量mutex解锁(这两步操作是原子的,不可分割),以便其它线程能进入临界区修改变量。 
    (2) Condition_signal(): 线程调用此函数后,除了当前线程继续往下执行以外; 操作系统同时做如下动作:从condition_wait()中进入睡眠的线程中选一个线程唤醒, 同时被唤醒的线程试图锁(lock)住互斥量mutex, 当成功锁住后,线程就从condition_wait()中成功返回了。 

    2. 函数接口 

    C代码  收藏代码
    1. pthread: pthread_cond_wait/pthread_cond_signal/pthread_cond_broadcast()  
    2. Java: Condition.await()/Condition.signal()/Condition.signalAll()  



    3. 虚假唤醒(spurious wakeup)在采用条件等待时,我们使用的是 

    Java代码  收藏代码
    1. while(条件不满足){  
    2.    condition_wait(cond, mutex);  
    3. }  
    4. 而不是:  
    5. If( 条件不满足 ){  
    6.    Condition_wait(cond,mutex);  
    7. }   



    这是因为可能会存在虚假唤醒”spurious wakeup”的情况。 
    也就是说,即使没有线程调用condition_signal, 原先调用condition_wait的函数也可能会返回。此时线程被唤醒了,但是条件并不满足,这个时候如果不对条件进行检查而往下执行,就可能会导致后续的处理出现错误。 
    虚假唤醒在linux的多处理器系统中/在程序接收到信号时可能回发生。在Windows系统和JAVA虚拟机上也存在。在系统设计时应该可以避免虚假唤醒,但是这会影响条件变量的执行效率,而既然通过while循环就能避免虚假唤醒造成的错误,因此程序的逻辑就变成了while循环的情况。 
    注意:即使是虚假唤醒的情况,线程也是在成功锁住mutex后才能从condition_wait()中返回。即使存在多个线程被虚假唤醒,但是也只能是一个线程一个线程的顺序执行,也即:lock(mutex)   检查/处理  condition_wai()或者unlock(mutex)来解锁. 

    4. 解锁和等待转移(wait morphing) 

    解锁互斥量mutex和发出唤醒信号condition_signal是两个单独的操作,那么就存在一个顺序的问题。谁先随后可能会产生不同的结果。如下: 
    [color=red](1) 按照 unlock(mutex); condition_signal()顺序, 当等待的线程被唤醒时,因为mutex已经解锁,因此被唤醒的线程很容易就锁住了mutex然后从conditon_wait()中返回了。 

    C代码  收藏代码
    1. //...  
    2. unlock(mutex);    
    3. condition_signal(cond);  



    (2) 按照 condition_signal(); unlock(mutext)顺序,当等待线程被唤醒时,它试图锁住mutex,但是如果此时mutex还未解锁,则线程又进入睡眠,mutex成功解锁后,此线程在再次被唤醒并锁住mutex,从而从condition_wait()中返回。 

    C代码  收藏代码
    1. //...  
    2. condition_signal(cond);  
    3. unlock(mutex);    



    [/color] 

    可以看到,按照(2)的顺序,对等待线程可能会发生2次的上下文切换,严重影响性能。因此在后来的实现中,对(2)的情况,如果线程被唤醒但是不能锁住mutex,则线程被转移(morphing)到互斥量mutex的等待队列中,避免了上下文的切换造成的开销。 -- wait morphing 

    编程时,推荐采用(1)的顺序解锁和发唤醒信号。而Java编程只能按照(2)的顺序,否则发生异常!!。 

    在SUSv3http://en.wikipedia.org/wiki/Single_UNIX_Specification的规范中(pthread),指明了这两种顺序不管采用哪种,其实现效果都是一样的。

    看过apue大家都知道互斥器用于排他性的访问共享数据而不是等待原语,如果需要等待某个条件发生需要用条件变量。而当用条件变量的时候需要检查某个布尔表达式是否为真,进行这项检查的时候需要互斥器来保护,所以此时互斥器和条件变量联合起来用于同步。

    互斥器和条件变量用法如下:
    pthread_mutex_lock(&lock);
    while (condition_is_false) {
        pthread_cond_wait(&cond, &lock);
    }

    上面那个while能换成if吗?答案是不能,否则会导致spurious wakeup虚假唤醒。因为不仅要在pthread_cond_wait前要检查条件是否成立,在pthread_cond_wait之后也要检查。因为pthread_cond_wait不仅能被pthread_cond_signal/pthread_cond_broadcast唤醒,而且还会被其它信号唤醒,后者就是虚假唤醒。

    linux的pthread_cond_wait是用futex系统调用,这个是慢速系统调用,看过apue知道任何慢速系统调用被信号打断的时候会返回-1,并且把errno置为EINTR,如果慢速系统调用的重启功能被关闭,需要在调用该系统调用的地方手动重启它,像下面这样:

    while (1) {
        int ret = syscall();
        if (ret < 0 && errno == EINTR)
            continue;
        else
            break;
    }

    但是futex不能这么用,因为futex结束后到再次重启这个过程有个时间窗,在这个窗口内可能发生了pthread_cond_signal/phread_cond_broadcast,如果发生这种情况,再进行pthread_cond_wait的时候就错过了一次条件变量的变化,就会无限等待下去。但是如果不像上面那样写又无法重启futex系统调用,咋整呢?这就回到了上面检查布尔条件的时候为什么用while而不用if。

    用while不会因为虚假唤醒而错过phread_cond_signal/pthread_cond_broadcast,而且在通过判断while条件不成立检测出此次唤醒为虚假唤醒并继续调用futex继续等待。

  • 相关阅读:
    What's wrong with this code ?
    自己实现的一个Script Callback
    一个给文档评分的WebPart
    Portal中的列表不能设置权限?
    TechED2004 广州 参会计划
    SharePoint站点中用户信息与AD用户信息的“不一致”问题
    上周进行的SPS Training部分Session的资料
    过去的2004,开始的2005
    一个示范性的文档库结构TreeView WebPart
    TechED2004广州 第一天
  • 原文地址:https://www.cnblogs.com/kex1n/p/7030707.html
Copyright © 2020-2023  润新知