文章目录
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;
}
分析:链表会将之前没有出来的先压在下面,直到前面的输出完才会将他们输出来
来源:CSDN
作者:学无止境966
链接:https://blog.csdn.net/qq_23929673/article/details/98723986