C多线程编程---条件变量

1、原理

假设我们需要解决这样一个问题:一个列表记录需要处理的任务。一个线程往此列表添加任务,一个线程processTask处理此列表中的任务。这个问题的一个关键点在于processTask怎么判断任务列表不为空。一般有两种方法:
一. processTask线程不断查询任务列表是否为空。
二. 当列表不为空的时候,通知processTask处理相关任务。

第一种方法往往是在一个while循环中判断列表是否为空,如果为空则睡眠一段时间,如果不为空那么把任务取出来并加以处理。此方案需要一个睡眠时间的平衡点如果睡眠时间太长,任务得不到及时的处理,降低效率。如果睡眠时间过短占用CPU资源,却什么都不做,浪费CPU做其它事情的时间。
第二种方法就比较靠谱了,只有当列表不为空的时候才占用CPU的时间,其它时间什么都不做除了睡觉(线程挂起)。此方案就是我们所说的条件变量(condition variable)。

一般条件变量(condition variable)和互斥量结合使用。条件变量(condition variable)用途线程间资源的同步,互斥量(mutex)用途资源的互斥(唯一访问)。 一个通俗易懂的例子: 你上厕所的时候,条件变量告诉你厕所是否为空位,有空位你上,没空位你看着别人上。有空位的时候你不可能跟别人一起吧,所以你得给卫生间上锁也就是互斥量了。
线程a等待某个条件成立,条件成立,线程a才继续向下执行。线程b的执行使条件成立,条件成立之后唤醒线程a,以继续执行。这个条件就是条件变量
pthread_cond_t 就是条件变量类型

2、头文件

#include <pthread.h>

3、 函数库

3.1、初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //条件变量的静态初始化,定义变量可以用

    int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
        功能:初始化一个条件变量
        参数:
            cond:指定要初始化的条件变量(指向结构pthread_cond_t的指针)
            cond_attr:NULL 默认的(用于设置条件变量是进程内还是进程间的)
        返回值:0 成功
                非0 错误

3.2、pthread_cond_signal

int pthread_cond_signal(pthread_cond_t *cond);
    功能:启动在等待条件变量变为真的一个线程,每次最多可以给一个线程发送
    参数:
        cond:指定条件变量
    返回值:0 成功
         非0 错误

3.3、pthread_cond_broadcast

int pthread_cond_broadcast(pthread_cond_t *cond);
     功能:启动所有的等待条件变量为真的线程
     参数:
         cond:指定条件变量
     返回值:0 成功
             非0 错误

3.4、pthread_cond_wait

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);  //等待期间不会占用cpu,先解锁,然后进入睡眠状态,等待接受信号
     功能:等待条件变量为真(无条件等待)
     参数:
         cond:指定等待的条件变量
         mutex:等待之前需要解开的锁
     返回值:0 成功
             非0 错误

3.5、pthread_cond_timedwait

int   pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex, const struct timespec *abstime); //超时等待
     功能:超时等待,超时返回错误(计时等待)
     参数:
         cond:指定等待的条件变量
         mutex:等待之前需要解开的锁
         abstime:指定等待的时间
     返回值:0 成功
             非0 错误

3.6、pthread_cond_destroy

int pthread_cond_destroy(pthread_cond_t *cond);
     功能:销毁一个条件变量
     参数:
         cound:指定要销毁的条件变量
     返回值:0 成功
             非0 错误

3.7、操作步骤:

 1、解开mutex锁
 2、让线程等待条件变量为真
 3、条件变量为真时加

3.8、举例

    生产者和消费者(读者和写者)
    链表实现:生产者生产出来对象,放到链表头部,消费者从链表的头部取出消费

    第一个思考:两个线程如何同步访问链表的头部
    第二个思考:如果链表为空,消费者等待生产者生产对象
    第三个思考:生产者线程生产出对象需要通知消费者

        #include <stdio.h>
        #include <pthread.h>
        #include <stdlib.h>
        #include <time.h>

        typedef struct node{
            int num;
            struct node  *next;
        }node_t;

        typedef node_t *list_t;
        list_t head=NULL;
        pthread_mutex_t mutex=\
                    PTHREAD_MUTEX_INITIALIZER;
        pthread_cond_t cond=\
                PTHREAD_COND_INITIALIZER;

        //消费者线程
        void *consume(void *arg){
            node_t *tmp;
            while(1){
                //加锁
                pthread_mutex_lock(&mutex);//对临界变量加锁
                if(head==NULL)//如果头部指向空,等待
                    pthread_cond_wait(&cond,&mutex);
                tmp=head;//将要删除的节点赋值给中间值,然后头指针指向下一个
                head=head->next;//
                //解锁
                pthread_mutex_unlock(&mutex);
                //消费tmp节点
                printf("consum:%d\n",tmp->num);
                free(tmp);
                tmp=NULL;
                sleep(rand()%5);
            }
        }

        //生产者线程
        void *product(void *arg){//函数的格式void *(*start_routine) (void *),返回值是指针,参数也是
            node_t *n;
            while(1){
                //生产一个新的节点
                n=(node_t *)malloc(sizeof(node_t));
                n->num=rand()%1000+1;
                printf("p:%d\n",n->num);
            //加锁
                pthread_mutex_lock(&mutex);
                //将新节点放入到链表的头部
                n->next=head;
                head=n;
            //解锁
                pthread_mutex_unlock(&mutex);
                //通知消费者
                pthread_cond_signal(&cond);
                sleep(rand()%5);
            }
        }
        int main(void){
            pthread_t pid,cid;
            //设置随机数的种子
            srand(time(NULL));
            //创建两个线程,用于生产者和消费者
            pthread_create(&pid,NULL,product,NULL);//创建线程后即执行该线程
            pthread_create(&cid,NULL,consume,NULL);//参数含义是(存放ID的缓存区,NULL缺省属性,线程的执行函数,函数的唯一参数)
            //等待线程的汇合
            pthread_join(pid,NULL);
            pthread_join(cid,NULL);
            //销毁mutex锁
            pthread_mutex_destroy(&mutex);
            pthread_cond_destroy(&cond);
            return 0;
        }
        分析:链表会将之前没有出来的先压在下面,直到前面的输出完才会将他们输出来
发布了349 篇原创文章 · 获赞 6 · 访问量 9726

猜你喜欢

转载自blog.csdn.net/qq_23929673/article/details/98723986
今日推荐