条件变量
条件变量是 GNU/Linux 提供的第三种同步工具(第一互斥体第二这信号量);利用它你可以在多线程环境下实现更复杂的条件控制。
前言
引入条件变量的目的:在使用互斥锁的基础上引入条件变量可以使程序的效率更高,因为条件变量的引入明显减少了线程取竞争互斥锁的次数。执行pthread_cond_wait或pthread_cond_timedwait函数的线程明显知道了条件不满足,要因此在其释放锁之后就没有必要再跟其它线程去竞争锁了,只需要阻塞等待signal或broadcast函数将其唤醒。这样提高了效率。
# 一、线程条件变量是什么?
<font color=#999AAA 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
二、条件变量主要函数
主要应用函数
pthread_cond_t类型 用于定义条件变量 pthread_cond_t cond;
pthread_cond_init函数 pthread_cond_destroy函数
pthread_cond_wait函数 pthread_cond_timedwait函数
pthread_cond_signal函数 pthread_cond_broadcast函数
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
1.创建函数
pthread_cond_init函数:条件变量的创建分为静态创建和动态创建;
静态方式使用PTHREAD_COND_INITIALIZER常量,如下: pthread_cond_t cond=PTHREAD_COND_INITIALIZER
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 作用:初始化一个条件变量 参2:attr表条件变量属性,通常为默认值,传NULL即可。
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);
条件等待函数,主要是是实现 解锁-> 唤醒->枷锁
使用:pthread_cond_wati()用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex(互斥锁)配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()或pthread_cond_broadcast把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex。
一般在使用的时候都是在一个循环里使用pthread_cond_wait()函数,因为它在返回的时候不一定能拿到锁(这可能会发生饿死情形,当然这取决于操作系统的调度策略
4.条件变量超时等待
pthread_cond_timedwait函数:
int pthread_cond_timedwait(pthread_cond_t *restrict_cond, //条件变量 pthread_mutex_t *restrict_mutex, //互斥锁句柄 const struct timespec *restrict_abstime); 参数3:struct timespec结构体。 struct timespec {undefined 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 {undefined time_t tv_sec; /* seconds */ 秒 suseconds_t tv_usec; /* microseconds */ 微秒 };
作用:与pthread_cond_wait函数作用相同,但其限时等待一个条件变量,即如果在规定的时间点(第三个形参)还未被唤醒时,该线程自动唤醒并解除阻塞重新申请获取互斥锁:pthread_mutex_lock(&mutex);
5.单线程唤醒
pthread_cond_signal函数:
int pthread_cond_signal(pthread_cond_t *cond); 作用:唤醒至少一个阻塞在条件变量上的线程。
作用:pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。
但是pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程.
6.广播唤醒
pthread_cond_broadcast函数:
int pthread_cond_broadcast(pthread_cond_t *cond); 作用:唤醒全部阻塞在条件变量上的线程
编程注意
pthread_cond_signal()函数生效具有前提条件;
pthread_cond_signal只能唤醒已经处于pthread_cond_wait的线程;也就是说,如果signal的时候没有线程在condition wait装填,那么本次signal就失效没有效果,后续的线程进入condition wait之后,无法被之前的signal唤醒
例子
int x,y; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // Waiting until x is greater than y is performed as follows: pthread_mutex_lock(&mut); while(x <= y){ pthread_cond_wait(&cond,&mut); } /* operate on x and y */ pthread_mutex_unlock(&mut); // Modifications on x and y that may cause x to become greater than y should signal the con- // dition if needed: pthread_mutex_lock(&mut); /* modify x and y */ if(x > y) pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mut);