条件变量

条件变量使线程同步中一个很重要的概念,在之前的文章中我们也多次提及过。

条件变量

条件变量(cond)使在多线程程序中用来实现“等待--->唤醒”逻辑常用的方法,是进程间同步的一种机制。条件变量用来阻塞一个线程,直到条件满足被触发为止,通常情况下条件变量和互斥量同时使用。一般条件变量有两个状态:(1)一个/多个线程为等待“条件变量的条件成立“而挂起;(2)另一个线程在“条件变量条件成立时”通知其他线程。

为什么条件变量总是和互斥锁结合使用?

这其实有两方面的原因:

(1)互斥锁可以表示的状态的太少了,可以利用条件变量来增加有限的状态。

(2)条件变量虽然是线程同步的重要方法,但仅仅依靠条件变量是没有办法完成完成线程同步的工作的。

现在提出一个问题:

有两个线程,贡献一个全局变量count,count的初始值为0。这两个线程的任务是:线程1负责将count的的数值加到10,而线程而负责在线程1将count加到10之后将count输出后清零,这交替循环。

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>

int count=0;
pthread_mutex_t myMutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t myCond=PTHREAD_COND_INITIALIZER;

void* threadHandle1(void* argv)
{
    while(1)
    {
        pthread_mutex_lock(&myMutex);
        ++count;
        pthread_mutex_unlock(&myMutex);
        //留给其他线程足够的时间争用锁
        sleep(1);
    }
}

void* threadHandle2(void* argv)
{
    while(1)
    {
        //为了保证在线程进入临界区是,count的数值不会被修变。
        if(count==10)
        {
            pthread_mutex_lock(&myMutex);
            if(count==10)
            {
                printf("%d\n",count);
                count=0;
            }
            pthread_mutex_unlock(&myMutex);
        }
        printf("%d\n",count);
        sleep(1);
    }
}

int main()
{
    pthread_t pid[2];
    pthread_create(&pid[0],NULL,threadHandle1,NULL);
    pthread_create(&pid[1],NULL,threadHandle2,NULL);
    pthread_join(pid[0],NULL);
    pthread_join(pid[1],NULL);
    return 0;
}

虽然只是简单的两个线程对加法的运算,但线程1和线程2需要不停的交换锁的控制权,这样无疑就会给系统带来一些不必要的压力,原因是互斥锁只有两个状态(锁和不锁),而通过条件变量就会可以改进互斥锁在这一面的不足。

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>

int count=0;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void* threadHandle1(void* argv)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        ++count;
        printf("thread1(mutex):count=%d\n",count);
        pthread_mutex_unlock(&mutex);
        
        pthread_mutex_lock(&mutex);
        if(count==5)
        {
            if(pthread_cond_signal(&cond)==0)
            {
                printf("thread1:(count=5)signal\n");
            }
        }
        if(count>=10)
        {
            if(pthread_cond_signal(&cond)==0)
            {
                printf("thread1:(count=10)signal\n");
            }
        }
        pthread_mutex_unlock(&mutex);
        printf("thread1:(cond)unlock\n");
        sleep(1);
    }
}

void* threadHandle2(void* argv)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        while(count<10)
        {
            //为什么使用while?
            //防止signal唤醒的时机不对。
            printf("thread2(while):count=%d\n",count);
            //在函数返回之前将锁打开,在函数返回之后将锁关闭。
            pthread_cond_wait(&cond,&mutex);
            printf("condWait\n");
        }
        if(count>=10)
        {
            printf("thread2(if):count=%d\n",count);
            count=0;
        }
        pthread_mutex_unlock(&mutex);
        printf("mutexUnlock\n");
    }
}

int main()
{
    pthread_t pid[2];
    pthread_create(&pid[0],NULL,threadHandle1,NULL);
    sleep(1);
    pthread_create(&pid[1],NULL,threadHandle2,NULL);
    pthread_join(pid[0],NULL);
    pthread_join(pid[1],NULL);
    return 0;
}

 

代码解析:

pthread_cond_wait(&cond,&mutex);

该函数有三个作用:

(1)阻塞线程。

(2)将互斥锁解锁,并等待其他线程将其唤醒。(1)(2)为原子操作。

(3)在其他线程将其唤醒之后,将解锁的互斥锁重新加锁。

这里有两个问题:

(1)为什么要对线程2中的条件变量的部分加锁?

(2)在条件变量判断的时候为什么不用if而要使用while?

为什么要对线程2中的条件变量的部分加锁?

如果不加锁,在线程判断时假设这样一种情况:当线程1将count的数值加到9的时候,线程2去判断count的值,此时count的值还为9,那么线程2就会进入while循环中,等待线程1的条件成立,将自己唤醒。但就这这个时候,线程1还没有执行pthread_cond_wait时,线程1将count的值修改为10,并发送了signal信号,试图唤醒线程2。而线程2还没有执行wait所以并不会接收到这个信号,之后执行wait,而继续等待线程1的信号,但线程1会任务,自己已经将唤醒的信号发送了,这样就存在问题。

所以,需要在条件变量进行判断时,将变量锁住,让其他线程不能修改此变量,这样就可以保证在判断的时候条件的变量的值是正确的。即互斥锁的作用不是为了保护条件变量,而是为了保护条件判断时共享变量的值不会被修改

在条件变量判断的时候为什么不用if而要使用while?

这个主要是为了防止其他线程在条件变量的条件还不成立的情况下,将睡眠中的线程错误的唤醒。

就像刚才的程序中的情况:我们的想法是在线程1将count的结果加到10时,将线程2唤醒,但线程1却在count等于5时将线程2唤醒,如果这里使用if就会出现问题。即程序不能保证signal线程将wait线程唤醒的时机时正确的,所以需要多重判断,就需要使用while,而不是使用if。

signal唤醒线程的时机

pthread_cond_signal(&cond);

通过上面的代码的结果分析,可以看出pthread_cond_signal的功能只是唤醒一个被条件变量阻塞的线程,但该函数不会修改锁的状态。而pthread_cond_wait会修改互斥锁的状态。

这里存在这样一个问题:(1)先解锁,再唤醒;(2)先唤醒,再解锁。因为wait再被唤醒会会有加锁操作。

(1)先解锁互斥锁,再唤醒睡眠的线程。

优点:减少了线程再内核态了用户态切换的次数,减少了资源的消耗。因为唤醒线程和解锁,都是需要再内核态完成的,而先解锁,再唤醒,内核会一次将这两个操作完成,这样就减少了用户态和内核态切换的次数,从而节省了资源。

缺点:如果此时存在一个低优先级的线程在等待锁,那么一旦锁被释放,那么这个锁就会被低优先级的线程争抢去,而不会被wait的线程得到,导致wait线程阻塞,无法返回。

(2)先唤醒睡眠的线程,再解锁互斥锁。

优点:唤醒后的线程在等待为该互斥锁加锁,一旦锁被释放,wait线程就会立即加锁,而不会发生上述,锁被抢占额度情况。

缺点:会增加用户态到内核态切换的次数,增加资源的消耗。

虽然在语法这两个都可以,但一般在程序使用先唤醒,再解锁的方式。

  • 9
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值