欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux学习之旅(33) ---- 条件变量

程序员文章站 2022-05-31 23:29:34
...

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

条件变量

条件变量(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;
}

linux学习之旅(33) ---- 条件变量

 

代码解析:

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线程就会立即加锁,而不会发生上述,锁被抢占额度情况。

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

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

相关标签: 条件变量