目录
【条件变量】
1.条件变量概述
之前我们了解过的死锁的一种情况,就是两个线程通过管道互相读取数据的时候,读的线程抢到了互斥锁,但是由于管道的读端带阻塞功能,会使读线程阻塞,造成互斥锁无法解锁,导致写端的线程也阻塞,直接造成了死锁的出现。
我们可以通过设置管道读端不阻塞来解决这个问题,但是管道读端如果不阻塞的话,很难读取到数据,那么我们就要使用条件变量来解决这个问题
条件变量简单的说,就是休眠当前任务,然后当需要的条件满足的时候,再次唤醒休眠的任务。
通常来说,条件变量是和互斥锁一起使用的,也就是任务一因为在当前的任务中无法完成需要的条件,只能挂起当前任务,然后通过任务二来完成这个条件(通常就是操作线程之间共享的资源),当任务二完成条件后,会发送信号给休眠的任务一,休眠的任务一被唤醒然后向下执行。
2.条件变量的实现
这里我们以管道读写端阻塞造成的线程之间出现死锁来举例,如何通过条件变量让线程A无法完成的条件让线程B来完成。
实现步骤
当任务A一开始抢到互斥锁并且成功上锁后,由于公共资源中没有数据能够读取,所以只能等待条件满足(也就是有数据写入,但是由于任务A无法实现此功能,所以利用条件变量,休眠任务A,让任务B去执行),调用pthread_cond_wait,等待条件满足。pthread_cond_wait的内部其实就是解开了任务A当前的互斥锁,阻塞等待条件满足,然后让B成功上锁,等待B往公共资源写入数据(满足条件),然后B会发出信号通知休眠的任务A,解除A等待条件的阻塞,然后A再次阻塞与上互斥锁的,此时B解开互斥锁,让A重新上锁,然后A满足条件后成功执行,最后解除A互斥锁
3.条件变量的API
3.1条件变量的初始化
#include <pthread.h>
int pthread_cond_init(pthread_cond_t * cond,
const pthread_condattr_t * attr);
功能
初始化一个条件变量
参数
cond:需要初始化的条件变量的地址
attr:条件变量的属性,通常为NULL(设置默认)
也可以静态初始化 :pthread_cond_t cond = PTHREAD_COND_INITIALIZER
返回值
成功:0
失败:非0错误码
3.2条件变量的释放
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能
销毁一个条件变量
参数
cond:条件变量的地址
返回值
成功:0
失败:非0错误码
3.3等待条件
在条件变量中,如果一个线程需要等待一个条件满足,那么就需要用到条件变量,会阻塞在这里直到条件满足才会解阻塞。
其实底层就是打开当前线程的锁,然后使用其他线程来完成达到满足条件变量,再利用其他线程发出信号给阻塞的当前线程,解阻塞在重新上锁。(这几步是原子操作,底层实现)
#include <pthread.h>
int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t * mutex);
功能
timewait可以指定等待的时间
wait会一直阻塞直到条件满足
参数
cond:条件变量
mutex:互斥锁
返回值
成功:0
失败:非0错误码
3.4唤醒等待的条件变量
调用pthread_cond_wait会导致线程一直阻塞在当前,那么当条件变量满足的时候,该怎么解除阻塞的条件变量呢,需要用到pthread_cond_signal或者pthread_cond_broadcast
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
功能
唤醒阻塞的条件变量,pthread_cond_signal唤醒至少一个阻塞在线程上的条件变量,而pthread_cond_broadcast唤醒所有阻塞在条件变量上的线程
参数
cond:条件变量
返回值
成功:0
失败:非0错误码
4.利用互斥锁和条件变量实现生产者和消费者的关系
假如有一个仓库,当消费者和生产者之前只能单独进去购买或者存放货物(互斥),当仓库没有货物的时候,只能让生产者进去放东西然后再让消费者购买(条件变量)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
//定义一个全局变量表示商品数目
int num = 3;
//创建一把互斥锁
pthread_mutex_t mutex;
//创建一个条件变量
pthread_cond_t cond;
//生产者线程函数
void* creat_fun(void* arg)
{
while(1)
{
printf("生产者正在生产产品.....\n");
sleep(3);
//上锁
pthread_mutex_lock(&mutex);
//假设生产者3s生成一个
printf("生产者往仓库里面放了一个产品,放之前数目为%d\n",num);
num++;
//唤醒休眠线程
pthread_cond_broadcast(&cond);
printf("放之后数目为%d\n",num);
//解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}
//消费者线程创建
void* custmer1_fun(void* arg)
{
while(1)
{
//消费者1
if( 0 == strcmp( "消费者1" , (char*)arg ) )
{
//上锁
pthread_mutex_lock(&mutex);
/*条件变量创建,如果没有产品只能等待条件满足,
消费者是没有能力创建产品的*/
if( num == 0 )
{
printf("消费者1发现没有产品,等待生产\n");
pthread_cond_wait(&cond,&mutex);
}
//即使存在消费者抢锁,也只有仓库里面有东西才能够购买
if( num > 0 )
{
printf("消费者1进入仓库买产品,当前剩余%d个产品\n",num);
num--;
printf("消费者1购买完毕,当前剩余%d个产品\n",num);
printf("消费者1正在使用产品\n");
}
//解锁
pthread_mutex_unlock(&mutex);
//消费者使用产品需要3s
sleep(3);
}
}
return NULL;
}
//消费者线程创建
void* custmer2_fun(void* arg)
{
while(1)
{
//消费者2
if( 0 == strcmp( "消费者2" , (char*)arg ) )
{
//上锁
pthread_mutex_lock(&mutex);
/*条件变量创建,如果没有产品只能等待条件满足,
消费者是没有能力创建产品的*/
if( num == 0 )
{
printf("消费者2发现没有产品,等待生产\n");
pthread_cond_wait(&cond,&mutex);
}
//即使存在消费者抢锁,也只有仓库里面有东西才能够购买
if( num > 0 )
{
printf("消费者2进入仓库买产品,当前剩余%d个产品\n",num);
num--;
printf("消费者2购买完毕,当前剩余%d个产品\n",num);
printf("消费者2正在使用产品\n");
}
//解锁
pthread_mutex_unlock(&mutex);
//消费者使用产品需要3s
sleep(3);
}
}
return NULL;
}
int main(int argc, char const *argv[])
{
//初始化锁
pthread_mutex_init(&mutex, NULL);
//初始化条件变量
pthread_cond_init(&cond,NULL);
//创建三个线程,两个消费者一个生产者
pthread_t cus , cus2 , creat;
//生产者线程创建
pthread_create(&creat, NULL, creat_fun , "生产者");
//消费者线程创建
pthread_create(&cus, NULL, custmer1_fun , "消费者1");
pthread_create(&cus2, NULL, custmer2_fun , "消费者2");
//回收线程
pthread_join(creat, NULL);
pthread_join(cus, NULL);
pthread_join(cus2, NULL);
//销毁锁
pthread_mutex_destroy(&mutex);
//销毁条件变量
pthread_cond_destroy(&cond);
return 0;
}