我们来理解一下条件变量,通俗的来讲就是条件变量的作用在于给多个线程提供了一个汇合的场所。举个例子说明一下,运动会赛跑中,所有选手都会等到发令枪响后才开跑,把选手比作其他线程,发令员比作主线程,意思就是所有的线程都等待主线程给予一个可以运行的信号,如果没有给信号,那么将会阻塞下去。
1.条件变量的概念
上一篇中我们介绍的是互斥量,而条件变量与互斥量不同,互斥量是防止多线程同时访问共享的互斥变量来保护临界区,只有两种状态:锁定和非锁定。
而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足。简单的来说,就是设置一个条件变量让线程1等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,继续向下执行。条件变量总是和互斥量一起使用,互斥量保护着条件变量,防止多个线程对条件变量产生竞争。
当条件变量满足时,线程通常解锁并等待该条件变量发生变化,一旦另一个线程修改了条件变量,就会通知相应的条件变量唤醒一个或多个被该条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来讲,条件变量用于线程同步,当条件变量不满足时,允许其中一个执行流挂起和等待。
2.函数接口
条件变量的初始化&销毁函数
参数:
cond:条件变量
attr:条件变量属性
返回值:成功返回0,失败返回错误码
销毁函数所指定的条件变量,同时会释放所给它分配的资源。调用该函数的进程并不等待在参数所指定的条件变量上。
等待条件满足
pthread_cond_wait函数执行时先自动释放指定的锁,然后等待条件变量的变化,在函数返回之前,自动将指定的互斥量重新锁住。
pthread_cond_timewait函数与pthread_cond_wait的区别在于,如果达到或者超过所引用的参数abtime,将结束并发回错误ETIME。
唤醒等待
其中,pthread_cond_signal只唤醒一个在相同条件变量中阻塞的线程,pthread_cond_broadcast唤醒等待队列中所有线程。
注意:调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait最后一步是要将指定的互斥量重新锁住,若pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。
3.条件变量使用规范
等待条件
pthread_mutex_lock(&mutex);
while(条件为假)
ptnread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
给条件发送信号
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
4.举个典型例子->生产者消费者模型
- 满足互斥和同步条件,用互斥锁和条件变量实现
- 多个生产者和消费者,其中生产者和生产者属于互斥关系,生产者和消费者属于同步互斥关系,消费者和消费者属于竞争关系,需要互斥锁
生产者和消费者模型可以归结为:三种关系,两种角色,一个交易场所
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
typedef struct LinkNode{
int data;
struct LinkNode *next;
}Node, *pNode;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pNode Create(int data){
pNode NewNode = (pNode)malloc(sizeof(Node));
if(NewNode == NULL){
perror("malloc");
return NULL;
}
NewNode->data = data;
NewNode->next = NULL;
return NewNode;
}
void InitLink(pNode* head){
*head = Create(0);
}
int IsEmpty(pNode head){
assert(head);
if(head->next)
return 0;
else
return 1;
}
void PushFront(pNode head, int data){
assert(head);
pNode node = Create(data);
node->next = head->next;
head->next = node;
}
void PopFront(pNode head, int *data){
assert(head);
assert(data);
if(IsEmpty(head)){
printf("Link is empty\n");
return;
}
pNode del = head->next;
*data = del->data;
head->next = del->next;
free(del);
del->data = 0;
del->next = NULL;
}
void Display(pNode head){
assert(head);
pNode cur = head->next;
while(cur){
printf("%d ",cur->data);
cur = cur->next;
}
printf("\n");
}
void Destory(pNode head){
int data = 0;
assert(head);
while(!IsEmpty(head)){
PopFront(head, &data);
}
free(head);
}
void *product_run(void *arg){
int data = 0;
pNode head = (pNode)arg;
while(1){
usleep(1);
data=rand()%1000;
pthread_mutex_lock(&lock);
PushFront(head, data);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&cond);
printf("product is done, data=%d\n",data);
}
}
void *consumer_run(void *arg){
int data = 0;
pNode head = (pNode)arg;
while(1){
pthread_mutex_lock(&lock);
while(IsEmpty(head)){
pthread_cond_wait(&cond, &lock);
}
PopFront(head, &data);
pthread_mutex_unlock(&lock);
printf("consumer is done,data=%d\n",data);
}
}
void test()
{
pNode head = NULL;
InitLink(&head);
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, product_run, (void*)head);
pthread_create(&tid2, NULL, consumer_run, (void*)head);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
Destory(head);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
int main()
{
test();
}
注意看到这里wait等待的时候是一个while不是if,这是为了防止“惊群效应”,例如两个线程同时阻塞在wait,先后醒来,快的线程先做完处理后把条件改变了,并且对互斥量解锁,此时慢的线程在wait里获得了锁返回,再去做处理就会出问题。