条件变量
条件变量在多线程编程中,提供了线程等待-唤醒的逻辑模式。通过用户自己的条件判断去设置什么时候让线程进行等待,什么时候进行唤醒。
本质:线程等待队列+等待与唤醒接口
通过一个关于生产和消费的模型理解条件变量
//通过生产者和消费者,来体会对临界资源的访问时序性和对条件变量的使用
//本程序想要得到的效果是,保证两个线程执行的时序性。
//循环生产一个资源后再去消费一个资源。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//定义生产者和消费者条件变量,其实可以理解为定义了两个等待队列
pthread_cond_t _con;
pthread_cond_t _pro;
//定义互斥锁
pthread_mutex_t _mutex;
//初始化资源数量为0
int is_hava_resource = 0;
//声明两个线程执行函数
void *thr_con(void *arg);
void *thr_pro(void *arg);
int main()
{
//1.分别创建一个生产者线程和消费者线程
//2.分别设置线程执行函数,一个进行消耗资源,一个负责生产资源
//条件变量和互斥锁的初始化
pthread_cond_init(&_con,NULL);
pthread_cond_init(&_pro,NULL);
pthread_mutex_init(&_mutex,NULL);
pthread_t con_tid;
pthread_t pro_tid;
int ret;
ret = pthread_create(&con_tid,NULL,thr_con,NULL);
if(ret != 0)
{
perror("create error\n");
return -1;
}
ret = pthread_create(&pro_tid,NULL,thr_pro,NULL);
if(ret != 0)
{
perror("create error\n");
return -1;
}
pthread_join(con_tid,NULL);
pthread_join(pro_tid,NULL);
//条件变量和互斥锁的销毁
pthread_cond_destroy(&_con);
pthread_cond_destroy(&_pro);
pthread_mutex_destroy(&_mutex);
return 0;
}
void *thr_con(void *arg)
{
while(1)
{
pthread_mutex_lock(&_mutex);
while(is_hava_resource == 0)
{
pthread_cond_wait(&_con,&_mutex);//让消费者进行等待
}
is_hava_resource--;
printf("i get a resource\n");
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_pro);//唤醒生产者进行生产
}
}
void *thr_pro(void *arg)
{
while(1)
{
pthread_mutex_lock(&_mutex);
while(is_hava_resource == 1)
{
pthread_cond_wait(&_pro,&_mutex);//让生产者进行等待
}
is_hava_resource++;
printf("i make a resource\n");
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_con);//唤醒消费者进行消耗
}
}
执行结果:
条件变量使用需要理解的难点
一、为什么条件变量要搭配互斥锁使用
1.首先了解,pthread_wait封装了三个操作。
一、解锁
二、将其置入等待队列,阻塞,直到被唤醒。
三、上锁
其中步骤一和步骤二是原子操作。因为有上锁和解锁操作,所以要搭配互斥锁。
二、在进行条件判断的时候必须使用while而不能使用if
pthread_cond_signal是至少唤醒一个线程。若唤醒两个及以上的线程,
当第一个线程上锁后,其他线程就阻塞在上锁这一步操作了,
第一个线程在执行操作后会进行解锁,此时其他的线程就会继续向下运行,
但此时并没有资源可以使用。
因此,必须使用while再次判断,保证不会造成没有资源还对资源访问的操作。
可以根据下面图片理解
三、不同类型的线程为何需要不同的条件变量
这个例子还暴露不出这个问题。
上面说到pthread_cond_signal()在唤醒的时候,不是单一唤醒,是至少唤醒一个。
因此,若一个队列上有不同类型的线程,所有的线程可能都被唤醒,这不就不符合我们让其什么时候什么时候唤醒的逻辑了吗。