首页 > 程序人生 > 条件变量(Condition Variable)详解

条件变量(Condition Variable)详解

条件变量(Condtion Variable)是在多线程程序中用来实现“等待->唤醒”逻辑常用的方法。举个简单的例子,应用程序A中包含两个线程t1和t2。t1需要在bool变量test_cond为true时才能继续执行,而test_cond的值是由t2来改变的,这种情况下,如何来写程序呢?可供选择的方案有两种:

  • 第一种是t1定时的去轮询变量test_cond,如果test_cond为false,则继续休眠;如果test_cond为true,则开始执行。
  • 第二种就是上面提到的条件变量,t1在test_cond为false时调用cond_wait进行等待,t2在改变test_cond的值后,调用cond_signal,唤醒在等待中的t1,告诉t1 test_cond的值变了,这样t1便可继续往下执行。
  • 很明显,上面两种方案中,第二种方案是比较优的。在第一种方案中,在每次轮询时,如果t1休眠的时间比较短,会导致cpu浪费很厉害;如果t1休眠的时间比较长,又会导致应用逻辑处理不够及时,致使应用程序性能下降。第二种方案就是为了解决轮询的弊端而生的。然而条件变量在使用的过程中,比较容易出错,如何用得不正确的话,会适得其反的,接下来,我将详细分析如何来使用条件变量,希望能够给在使用条件变量过程中遇到问题的朋友有所帮助。
    在开始介绍之前,需要说明一下,在接下来的介绍中,需要用到互斥锁和条件变量相关的内容,在这里我以linux下的pthread_mutex_t为互斥锁类型,pthread_cond_t为条件变量类型来进行介绍,对pthread不熟的朋友,可以参考一下linux下的manual。
    1. 下面是把刚开始举的例子翻译后的程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    pthread_mutex_t mutex;  ///< 互斥锁
    pthread_cond_t  cond;   ///< 条件变量
    bool test_cond = false;
    /// TODO 初始化mutex和cond
     
    /// thread 1:
    pthread_mutex_lock(&mutex);            ///< 1
    while (!test_cond)
    {
        pthread_cond_wait(&cond, &mutex);  ///< 2,3
    }
    pthread_mutex_unlock(&mutex);          ///< 4
    RunThread1Func();
     
    /// thread 2:
    pthread_mutex_lock(&mutex);            ///< 5
    test_cond = true;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);          ///< 6
     
    /// TODO 销毁mutex和cond

    通过上面的例子,下面我来介绍一下条件变量在使用过程中需要注意的几点(也是比较容易出错的):
    (1)条件变量的使用过程中,最为关键的一点是互斥锁的使用。细心的朋友应该发现了,我在上面的例子中标了1、2、3、4、5、6个标号。在这里1、4、5、6都是正常的lock/unlock,2、3是需要特别说明的。2是进入pthread_cond_wait后的,pthread_cond_wait调的pthread_mutex_unlock,这样做的目的是为了保证在thread1阻塞wait后,thread2获取同一把锁mutex的时候,能够正常获取(即5,6)。3是thread1被唤醒后,要退出pthead_cond_wait之前,pthread_cond_wait调的pthread_mutex_lock,这样做的目的是为了把mutex的控制权还给调用pthread_cond_wait的线程(即thread1)。整理一下基本的时序为:

    1
    2
    3
    
    thread 1 lock->thread 1 wait-> thread 1 unlock(in wait)
    ->thread 2 lock->thread 2 signal->thread 2 unlock
    ->thread 1 lock(in wait)->thread 1 unlock

    (2)条件变量使用的过程中,通常会加一个bool或者int的值test_cond来配合使用。这里需要注意的一点是一定要在signal之前来改变test_cond,这样才能保证wait的线程被唤醒后,能够取到正确的test_cond的值,否则后果是不可预测的。

    1. chen3feng
      2011年4月26日07:03 | #1

      /// thread 1:
      pthread_mutex_lock(&mutex); ///< 1
      while (!test_cond) // 这里必须用 while,条条件一般还可能是表达式,比如 !queue.empty()
      {
      pthread_cond_wait(&cond, &mutex); ///< 2,3
      }
      pthread_mutex_unlock(&mutex); ///< 4
      RunThread1Func();

    2. 2011年4月26日09:20 | #2

      这个地方与应用相关的吧,如果只等一次,用if就够了,如果要等多次,就用while。不是必须用while吧。@chen3feng

    3. 2011年4月26日09:23 | #3

      峰哥大驾光临,受宠若惊啊,呵呵!@chen3feng

    4. 2011年4月26日09:26 | #4

      另外,你这个表达式的条件,本质也是一个bool,呵呵@chen3feng

    5. chen3feng
      2011年4月26日18:52 | #5

      小武哥 :
      这个地方与应用相关的吧,如果只等一次,用if就够了,如果要等多次,就用while。不是必须用while吧。@chen3feng

      wait 返回不代表条件成立,可能会有别的线程抢先又使得条件不成立了。

    6. 2011年4月27日21:18 | #6

      我看了下,感觉也是要while的。man pthread_cond_wait里面也有类似的例子,只用if的话感觉和前面介绍的等flase编程true的条件不太一致,看上去有点怪。

      这个函数看起来挺好用的,学到了。

    7. 2011年4月27日22:37 | #7

      恩,仔细想了下,是需要while的,我改一下@Leon

    8. 2011年4月27日22:39 | #8

      想了下,是需要while的,改过来了,多谢!@chen3feng

    9. kingwoo
      2016年4月1日15:32 | #9

      看你的博客访问量挺大的,还是指正几个问题吧
      这个代码里while (!test_cond),不优化没问题,优化后会进入死循环,需要用volatile修饰test_cond
      另外,pthread_cond_signal不一定非要在mutex里调用的

    1. 2011年11月6日11:41 | #1
    您必须在 登录 后才能发布评论.