条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所(共享的数据)。主要应用函数:
pthread_cond_init函数 pthread_cond_destroy函数
pthread_cond_wait函数 pthread_cond_timedwait函数
pthread_cond_signal函数 pthread_cond_broadcast函数
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
pthread_cond_t类型 用于定义条件变量 pthread_cond_t cond;
引入条件变量的目的:在使用互斥锁的基础上引入条件变量可以使程序的效率更高,因为条件变量的引入明显减少了线程取竞争互斥锁的次数。执行pthread_cond_wait或pthread_cond_timedwait函数的线程明显知道了条件不满足,要因此在其释放锁之后就没有必要再跟其它线程去竞争锁了,只需要阻塞等待signal或broadcast函数将其唤醒。这样提高了效率。
(1)pthread_cond_init函数
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
作用:初始化一个条件变量
参2:attr表条件变量属性,通常为默认值,传NULL即可。也可以使用静态初始化的方法,初始化条件变量:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
(2)pthread_cond_destroy函数
int pthread_cond_destroy(pthread_cond_t *cond);
作用:销毁一个条件变量
(3)pthread_cond_wait函数
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
作用:阻塞等待条件变量cond(形参1)满足条件,且释放已经掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex)(注意这是一个原子操作,即阻塞等待的同时马上解锁,类似sigsuspend函数);当被唤醒(signal或broadcast函数),pthread_cond_wait函数返回,解除阻塞并重新申请获取互斥锁:pthread_mutex_lock(&mutex);
(4)pthread_cond_timedwait函数
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
作用:与pthread_cond_wait函数作用相同,但其限时等待一个条件变量,即如果在规定的时间点(第三个形参)还未被唤醒时,该线程自动唤醒并解除阻塞重新申请获取互斥锁:pthread_mutex_lock(&mutex);
参数3:struct timespec结构体。
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
struct timespec结构体定义的是绝对时间(从1970年1月1日00:00:00开始计时的时间,unix操作系统的诞辰是1969年末)。time(NULL)返回的就是绝对时间(秒)。而alarm(1)是相对时间(相对于当前),相对当前时间定时1秒钟。
struct timespec t = {1, 0}; pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去)。 1970年1月1日 00:00:00秒作为计时元年。正确用法:
time_t cur = time(NULL); //获取当前时间(绝对时间)
struct timespec t; //定义timespec 结构体变量t
t.tv_sec = cur+1; //定时1秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参 参考APUE.11.6线程同步条件变量小节
在讲解setitimer函数时我们还提到另外一种时间类型:
struct timeval {
time_t tv_sec; /* seconds */ 秒
suseconds_t tv_usec; /* microseconds */ 微秒
};
(5)pthread_cond_signal函数
int pthread_cond_signal(pthread_cond_t *cond);
作用:唤醒至少一个阻塞在条件变量上的线程。
(6)pthread_cond_broadcast函数
int pthread_cond_broadcast(pthread_cond_t *cond);
作用:唤醒全部阻塞在条件变量上的线程
(7)生产者消费者条件变量模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
看如下示例,使用条件变量模拟生产者、消费者问题:
//生产者消费者条件变量模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
struct production {
int num;
struct production *next;
};
struct production *head = NULL; //定义全局指针head
struct production *rer = NULL; //定义全局指针rer
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //静态初始化
void *productor( void *arg ) //生产者
{
srand( time( NULL ) );
while(1){
pthread_mutex_lock( &mutex );
rer = (struct production *)malloc( sizeof(struct production) );
rer->num = (rand( )%400 + 1);
rer->next = head;
head = rer;
printf("--------The production is %d.\n",rer->num);
pthread_mutex_unlock( &mutex );
pthread_cond_signal( &cond ); //唤醒因wait阻塞的线程
sleep( rand( )%3 );
}
return NULL;
}
void *consumer( void *arg ) //消费者
{
srand( time( NULL ) );
while(1){
pthread_mutex_lock( &mutex );
while( head == NULL ) //注意不能是if,必须采用循环
pthread_cond_wait( &cond, &mutex );
rer = head;
head = rer->next;
printf("++++++++The consumer is %d.\n",rer->num);
pthread_mutex_unlock( &mutex );
sleep( rand( )%3 );
}
return NULL;
}
int main( void )
{
pthread_t pid, cid;
int ret;
ret = pthread_create( &pid, NULL, productor, NULL);
if( ret != 0)
{
fprintf(stderr,"pthread_create error1: %s\n",strerror(ret));
exit(1);
}
ret = pthread_create( &cid, NULL, consumer, NULL);
if( ret != 0)
{
fprintf(stderr,"pthread_create error2: %s\n",strerror(ret));
exit(1);
}
ret = pthread_join(pid,NULL);
if( ret != 0)
{
fprintf(stderr,"pthread_join error1: %s\n",strerror(ret));
exit(1);
}
ret = pthread_join(cid,NULL);
if( ret != 0)
{
fprintf(stderr,"pthread_join error2: %s\n",strerror(ret));
exit(1);
}
pthread_cond_destroy(&cond); //销毁条件变量
pthread_mutex_destroy(&mutex); //销毁互斥锁
return 0;
}
[root@localhost 02_pthread_sync_test]# ./pthrd_cond
--------The production is 257.
++++++++The consumer is 257.
--------The production is 324.
--------The production is 327.
++++++++The consumer is 327.
--------The production is 180.
--------The production is 313.
++++++++The consumer is 313.
--------The production is 285.
++++++++The consumer is 285.
++++++++The consumer is 180.
++++++++The consumer is 324.
--------The production is 21.
--------The production is 351.
++++++++The consumer is 351.
分析:
- 将生产者与消费者共享的资源(产品)用链表这种数据结构表示,head始终指向链表表头,rer用于向链表中增加或删除元素时的中间过渡。注意:这里的head与rer都是全局指针,都会被所有的消费者和生产者线程所共享,因此访问这两个资源时,都必须采用互斥锁,即先加锁,再访问,最后解锁。否则容易出现段错误。
- 这个程序只是创建了一个生产者线程和一个消费者线程,但是考虑的时候应该考虑有多个生产者线程和多个消费者线程。每一个线程都是采用互斥锁访问共享资源head和rer。通过采用条件变量把线程阻塞原因分为三部分:1.生产者因为加锁时(锁未解锁)而阻塞;2.消费者因为加锁时(锁未解锁)而阻塞;3.消费者因为执行了wait而阻塞(这是因为条件不满足)。这三部分线程,每当有锁解锁时,只有前两部分线程会去竞争,而第三部分线程只有等待被唤醒后才会去竞争。因此采用条件变量可以减少互斥锁的竞争次数。
- 上述程序中之所以必须采用while,是因为当因为wait阻塞而唤醒的线程,即使抢到了锁,公共资源也可能被其余消费者消耗了(因为生产者在signal之前会解锁,而解锁会引发消费者再次竞争锁),因此必须再次判断共享资源是否为空。
- 这道题目如果对共享资源的最大数量有限制,那么生产者线程也要引入条件变量。
- 相较于mutex而言,条件变量可以减少竞争。如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。