Linux线程同步:细说 互斥锁+条件变量

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35865125/article/details/88651266

假设有多个线程共享的资源sum, 与之相关联的mutex 是lock_s. 假设每个线程对sum的操作很简单,与sum的状态无关,比如只是sum++. 那么只用mutex足够了. 程序员只要确保每个线程操作前,取得lock,然后sum++,再unlock即可. 每个线程的对sum的操作代码将像这样:

add()
{
    pthread_mutex_lock(lock_s);
    sum++;
    pthread_mutex_unlock(lock_s);
}


如果操作比较复杂,假设线程t0,t1,t2的操作是sum++,而线程t3则是在sum到达100的时候,打印出一条信息,并对sum清零. 这种情况下,如果只用互斥锁, 则t3需要一个循环,每个循环里先取得lock_s,然后检查sum的状态,如果sum>=100,则打印并清零,然后unlock.如果sum<100,则unlock,并sleep()本线程合适的一段时间。

这个时候,t0,t1,t2的代码不变,t3的代码如下

while (1)
{
    pthread_mutex_lock(lock_s);
    if(sum<100)
    {
        printf(“sum reach 100!”);

        sum = 0;
        pthread_mutex_unlock(lock_s);
    }
    else
    {
        pthread_mutex_unlock(lock_s);
        my_thread_sleep(100);
      
    }
}

这种办法有两个问题
1) sum在大多数情况下不会到达100,那么对t3的代码来说,大多数情况下,走的是else分支,只是lock和unlock,然后sleep().这浪费了CPU处理时间.
2) 为了节省CPU处理时间,t3会在探测到sum没到达100的时候sleep()一段时间.这样却又带来另外一个问题,亦即t3响应速度下降.可能在sum到达200的时候,t4才会醒过来.
3) 这样,程序员在设置sleep()时间的时候陷入两难境地,设置得太短了节省不了资源,太长了又降低响应速度.真是难办啊!

这个时候,condition variable从天而降,拯救了焦头烂额的你.


首先定义一个condition variable.
pthread_cond_t  cond_sum_ready = PTHREAD_COND_INITIALIZER;

t0,t1,t2的代码只要后面加两行,像这样
add()
{
    pthread_mutex_lock(lock_s);
    sum++;
    pthread_mutex_unlock(lock_s);
    if(sum>=100)
       pthread_cond_signal(&cond_sum_ready);
}
而t3的代码则是
while(1)
{
    pthread_mutex_lock(lock_s);
    while(sum<100)
        pthread_cond_wait(&cond_sum_ready, &lock_s);
    printf(“sum is over 100!”);
    sum=0;
    pthread_mutex_unlock(lock_s);
    return OK;
}

注意两点:
1) 在thread_cond_wait()之前,必须先lock相关联的mutex。

 pthread_cond_wait()实际上会先unlock该mutex(如果前面不加lock的话,这个unlock动作可能将别的线程的锁给unlock掉,导致出错(self)), 然后block,在目标条件满足后再重新lock该mutex, 然后返回.


2) 为什么是while(sum<100),而不是if(sum<100) ?这是因为在pthread_cond_signal()和pthread_cond_wait()返回之间,有时间差,假设在这个时间差内,还有另外一个线程t4又把sum减少到100以下了,那么t3在pthread_cond_wait()返回之后,显然应该再检查一遍sum的大小.这就是用 while的用意.

另外,对于这里提到的,引出另外一个话题:
pthread_cond_signal即可以放在pthread_mutex_lock和pthread_mutex_unlock之间,也可以放在pthread_mutex_lock和pthread_mutex_unlock之后,各有有缺点。

a) 放在之间:
pthread_mutex_lock
    xxxxxxx
pthread_cond_signal
pthread_mutex_unlock

缺点:在某些线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal),然后又回到内核空间(因为cond_wait返回后会有原子加锁的 行为)(意思是说这时候发送signal的线程还没有执行unlock,所以wait的线程进行lock会导致堵塞,并进入内核),所以一来一回会有性能的问题。
但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。所以在Linux中推荐使用这种模式。

a) 放在之后:
pthread_mutex_lock
    xxxxxxx
pthread_mutex_unlock
pthread_cond_signal


优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了
缺点:如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程),而这在上面的放中间的模式下是不会出现的。所以最好还是用

while(sum<100)
        pthread_cond_wait(&cond_sum_ready, &lock_s);
当唤醒之后,再检查以下是否满足条件。

 

信号丢失问题现象(对于这种现象个人认为需要看具体的业务逻辑,来判断是否有你有影响)

线程3处于下面的状态:

在调用pthread_cond_wait()函数,但线程3还没有进入wait cond的状态的时候,  或者,正处在测试条件变量和调用pthread_cond_wait函数之间。 这时没有线程正在处在阻塞等待的状态下,此时线程1调用了 cond_singal, 这种情况下,线程1的这个cond_singal就丢失了。

 

Ref:

https://www.cnblogs.com/lonelycatcher/archive/2011/12/20/2294161.html

https://www.cnblogs.com/charlesblc/p/6143397.html

my: https://blog.csdn.net/qq_35865125/article/details/85144149

猜你喜欢

转载自blog.csdn.net/qq_35865125/article/details/88651266