1. 概念
- 条件变量是线程可用的另一种同步机制
- 条件变量给多个线程提供了一个会合的场所
- 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生
- 条件变量是线程中的东西,就是等待某一条件的发生,和信号一样
2. 使用场景
- 条件变量要与互斥量一起使用,条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量
- 其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件
3. 数据类型
- pthread_cond_t
4. 条件变量初始化与释放
①静态初始化
- 直接把pthread_cond_t定义的条件变量设置为常量PTHREAD_COND_INITIALIZER
- 静态初始化条件变量只能拥有默认的条件变量属性,不能设置其他条件变量属性
pthread_cond_t cond; cond=PTHREAD_COND_INITIALIZER; //或者 pthread_cond_t *cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t)); *cond=PTHREAD_COND_INITIALIZER;
②动态初始化
- 静态初始化条件变量只能拥有默认条件变量属性,我们可以通过pthread_mutex_init函数来动态初始化条件变量,并且可以在初始化时选择设置条件变量的属性
#include <pthread.h> int pthread_cond_init(pthread_cond_t* restrict cond,const pthread_condattr_t* restrict attr); int pthread_cond_destroy(pthread_cond_t* cond); //返回值:成功返回0;失败返回错误编号
pthread_cond_init:
- 功能:对条件变量初始化
- 参数:
- 参数1: 需要初始化的条件变量
- 参数2:初始化时条件变量的属性。如果使用默认属性,此处填NULL
pthread_cond_destroy:
- 功能:对条件变量反初始化(在条件变量释放内存之前)
- 参数:条件变量
- 备注(重点):此函数只是反初始化互斥量,并没有释放内存空间,如果互斥量是通过malloc等函数申请的,那么需要在free掉互斥量之前调用pthread_mutex_destroy函数
pthread_cond_t cond; pthread_cond_init(&cond,NULL); /*do something*/ pthread_cond_destroy(&cond);
pthread_cond_t * cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t)); pthread_cond_init(cond,NULL); /*do something*/ pthread_cond_destroy(cond); free(cond);
5. 等待条件变量
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t* restrict cond,pthread_mutex_t* restrict mutex);
int pthread_cond_timedwait(pthread_cond_t* cond,pthread_mutex_t* restrict mutex,const struct timespec* restrict tsptr);
//返回值:成功返回0;失败返回错误编号
- 这两个函数调用成功返回时,线程需要重新计算条件,因为另一个线程可能已经在运行并改变了条件
pthread_cond_wait
注意:等待条件变量变为真
如何使用:参数mutex互斥量提前锁定,然后该互斥量对条件进行保护,等待参数1cond条件变量变为真。在等待条件变量变为真的过程中,此函数一直处于阻塞状态。但是处于阻塞状态的时候,mutex互斥量被解锁(因为其他线程需要使用到这个锁来使条件变量变为真)
当pthread_cond_wait函数返回时,互斥量再次被锁住
pthread_cond_timedwait
- pthread_cond_timedwait函数与pthread_cond_wait函数功能相同。不过多了一个超时参数。超时值指定了我们愿意等待多长时间,它是通过timespec结构体表示的
- 如果超时到期之后,条件还是没有出现,此函数将重新获取互斥量,然后返回错误ETIMEOUT
- 注意:这个时间值是一个绝对数而不是相对数。例如,假设愿意等待3分钟,那么不是把3分钟转换为timespec结构体,而是需要把当前时间加上3分钟再转换成timespec结构
下面定义了一个得到超时值的绝对时间获取函数:
- 可以使用clock_gettime函数获取timespec结构表示的当前时间。但是目前并不是所有的平台都支持这个函数。因此,可以使用gettimeofday获取timeval结构表示的当前时间,然后把这个时间转换为timespec结构
#include <sys/time.h> #include <stdlib.h> void maketimeout(struct timespec *tsp, long minutes) { struct timeval now; /* get the current time */ gettimeofday(&now, NULL); tsp->tv_sec = now.tv_sec; tsp->tv_nsec = now.tv_usec * 1000; /* usec to nsec */ /* add the offset to get timeout value */ tsp->tv_sec += minutes * 60; }
6. 条件变量信号发送
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
//返回值:成功返回0;失败返回错误编号
- 这两个函数用于通知线程条件变量已经满足条件(变为真)。在调用这两个函数时,是在给线程或者条件发信号
- 必须注意:一定要在改变条件状态以后再给线程发信号
- pthread_cond_signal函数:至少能唤醒一个等待该条件的线程
- pthread_cond_broad函数:则唤醒等待该条件的所有线程
7. 示例
#include <pthread.h>
struct msg {
struct msg *m_next;
/* ... more stuff here ... */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void process_msg(void)
{
struct msg *mp;
for (;;) {
pthread_mutex_lock(&qlock);
while (workq == NULL)
pthread_cond_wait(&qready, &qlock);
mp = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock);
/* now process the message mp */
}
}
void enqueue_msg(struct msg *mp)
{
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready);
}
8. 案例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <strings.h>
#define BUFFSIZE 2
struct prodcons
{
char buff[BUFFSIZE];
int write_index;
int read_index;
pthread_cond_t notempty;
pthread_cond_t notfull;
pthread_mutex_t lock;
};
void init(struct prodcons *pro);
void* producer_func(void* arg);
void* customer_func(void* arg);
void put(struct prodcons *pro,int data);
int get(struct prodcons *pro);
int main()
{
pthread_t producer,customer;
struct prodcons pro;
init(&pro);
if(pthread_create(&producer,NULL,producer_func,&pro)!=0){
perror("pthread_create");
exit(EXIT_FAILURE);
}
if(pthread_create(&customer,NULL,customer_func,&pro)!=0){
perror("pthread_create");
exit(EXIT_FAILURE);
}
if(pthread_join(producer,NULL)!=0){
perror("pthread_join");
exit(EXIT_FAILURE);
}
if(pthread_join(customer,NULL)!=0){
perror("pthread_join");
exit(EXIT_FAILURE);
}
exit(0);
}
void init(struct prodcons *pro)
{
bzero(pro->buff,sizeof(pro->buff));
pro->write_index=0;
pro->read_index=0;
if((pthread_cond_init(&pro->notempty,NULL)!=0)
||(pthread_cond_init(&pro->notfull,NULL)!=0)
||(pthread_mutex_init(&pro->lock,NULL)!=0)){
perror("init");
exit(EXIT_FAILURE);
}
}
void* producer_func(void* arg)
{
int i;
for(i=1;i<=5;++i)
{
printf("producer sleep 1 second to produce..\n");
sleep(1);
printf("put %d to produce\n",i);
put(arg,i);
}
for(i=6;i<=10;++i)
{
printf("producer sleep 3 second to produce..\n");
sleep(3);
printf("produce:put %d\n",i);
put(arg,i);
}
put(arg, -1);
printf("producer exit\n");
pthread_exit(0);
}
void* customer_func(void* arg)
{
int data;
while(1)
{
printf("customer wait 2 second to consumption.\n");
sleep(2);
data=get(arg);
printf("customer:get %d\n",data);
if(data==-1)
break;
}
printf("customer exit\n");
pthread_exit(0);
}
void put(struct prodcons *pro,int data)
{
pthread_mutex_lock(&pro->lock);
//如果满了,等到非满条件变量产生
while(((pro->write_index+1)%BUFFSIZE) == pro->read_index)
{
printf("producer is full,wait not full");
pthread_cond_wait(&pro->notfull,&pro->lock);
}
pro->buff[pro->write_index]=data;
pro->write_index++;
if(pro->write_index>=BUFFSIZE)
pro->write_index=0;
pthread_cond_signal(&pro->notempty);
pthread_mutex_unlock(&pro->lock);
}
int get(struct prodcons *pro)
{
int data;
pthread_mutex_lock(&pro->lock);
//如果空了,则等待非空
while(pro->write_index==pro->read_index)
{
printf("customer is empty,wait not empty\n");
pthread_cond_wait(&pro->notempty,&pro->lock);
}
data=pro->buff[pro->read_index];
pro->read_index++;
if(pro->read_index>=BUFFSIZE)
pro->read_index=0;
pthread_cond_signal(&pro->notfull);
pthread_mutex_unlock(&pro->lock);
return data;
}