有了互斥锁,为什么还要条件变量?

为了解决这个问题,首先要非常深入了解每一个概念:

1. 互斥锁(mutual exclusive lock variable / mutex )

互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥锁加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥锁加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。

2. 条件变量

条件变量(cond)是在多线程程序中用来实现"等待--》唤醒"逻辑常用的方法。条件变量利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使“条件成立”。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。线程在改变条件状态前必须首先锁住互斥量,函数pthread_cond_wait把自己放到等待条件的线程列表上,然后对互斥锁解锁(这两个操作是原子操作)。在函数返回时,互斥量再次被锁住。

那为什么有互斥锁,还需要条件变量?

因为:互斥锁和条件变量所解决的,是不同的问题,不同的场景。

互斥锁解决的是在 shared memory space 模型下,多个线程对同一个全局变量的访问的竞争问题。由于写操作的非原子性(从内存中读进寄存器,修改,如果其他线程完成了对这个变量的修改,则旧的修改就被覆盖,等等问题),必须保证同一时间只有一个线程在进行写操作。这就涉及到了互斥锁,将临界区的操作锁起来,保证只有一个线程在进行操作。多个线程在等待同一把锁的时候,按照 FIFO 组织队列,当锁被释放时,队头线程获得锁(由操作系统管理,具体不表)。没有获得锁的线程继续被 block,换言之,它们是因为没有获得锁而被 block

假如我们没有“条件变量”这个概念,如果一个线程要等待某个“自定义的条件”满足而继续执行,而这个条件只能由另一个线程来满足,比如 T1不断给一个全局变量 x +1, T2检测到x 大于100时,将x 置0,如果我们没有条件变量,则只通过互斥锁则可以有如下实现:

/* 
 * Assume we have global variables:
 * int iCount == 0;
 * pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 */

//thread 1:

while(true)
{
    pthread_mutex_lock(&mutex);
    iCount++;
    pthread_mutex_unlock(&mutex);
}


//thread 2:
while(true)
{
    pthread_mutex_lock(&mutex);
    if(iCount >= 100)
    {
        iCount = 0;
    }
    pthread_mutex_unlock(&mutex);

}

这种实现下,就算 lock 空闲,thread2需要不断重复<加锁,判断,解锁>这个流程,会给系统带来不必要的开销。有没有一种办法让 thread2先被 block,等条件满足的时候再唤醒 thread2?这样 thread2 就不用不断进行重复的加解锁操作了?这就要用到条件变量了:


//thread1 :
while(true)
{
    pthread_mutex_lock(&mutex);
    iCount++;
    pthread_mutex_unlock(&mutex);

    pthread_mutex_lock(&mutex);
    if(iCount >= 100)
    {
        pthread_cond_signal(&cond);
    }
    pthread_mutex_unlock(&mutex);
}

//thread2:
while(1)
{
    pthread_mutex_lock(&mutex);
    while(iCount < 100)
    {
        pthread_cond_wait(&cond, &mutex);
    }
    printf("iCount >= 100\r\n");
    iCount = 0;
    pthread_mutex_unlock(&mutex);
}

需要注意的是,条件变量需要配合互斥锁来使用:
为什么要与pthread_mutex 一起使用呢? 这是为了应对 线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候,此时线程2调用了 cond_singal 的情况。 如果不用mutex锁的话,这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cod_wait() 释放锁并进入wait_cond状态 ,此时线程2上锁) 的时候才能调用cond_singal.

简而言之就是,在thread 1 call pthread_cond_wait() 的时刻到 thread 1真正进入 wait 状态时,是存在着时间差的。如果在这段时间差内 thread2 调用了 pthread_cond_signal() 那这个 signal 信号就丢失了。给 wait 加锁可以防止同时有另一个线程在 signal。

(好像还有其他原因,有空补。)

推荐阅读更多精彩内容

  • 前言 在多线程开发中,常会遇到多个线程访问修改数据。为了防止数据不一致或数据污染,通常采用加锁机制来保证线程安全。...
    赵梦楠阅读 600评论 0 5
  • 引用自多线程编程指南应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两个线程同时修改同一资源有...
    Mitchell阅读 1,689评论 1 7
  • linux线程同步 信号灯:与互斥锁和条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用...
    鲍陈飞阅读 551评论 0 2
  • 简介 线程创建 线程属性设置 线程参数传递 线程优先级 线程的数据处理 线程的分离状态 互斥锁 信号量 一 线程创...
    壹号T馆阅读 6,535评论 1 6
  • 成功的路,不怕万人阻挡,只怕自己投降;成长的帆,不怕狂风巨浪,只怕自己没胆量!有路,就大胆去走;有梦,就大胆飞翔。...
    安全保卫处阅读 166评论 0 0
  • 票,反方向了 弗兰克 当大脑感觉很累的时候,此时此刻应该稍微冷静一下,用嘴“读出来”,让口和神经保持一致的“验算”...
    北京那个啥阅读 104评论 0 0
  • 怎么开始说呢 其实写这个的初衷是为了纪录我和饼饼以前一些我自认为蛮甜的片段 然后为什么要纪录和饼饼的呢 我在(1)...
    青春胖阅读 112评论 0 0