什么是条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
条件变量类型为 pthread_cond_t。
另一个解释:
条件变量是线程的另外一种同步机制,这些同步对象为线程提供了会合的场所,理解起来就是两个(或者多个)线程需要碰头(或者说进行交互-一个线程给另外的一个或者多个线程发送消息),我们指定在条件变量这个地方发生,一个线程用于修改这个变量使其满足其它线程继续往下执行的条件,其它线程则接收条件已经发生改变的信号。
条件变量同锁一起使用使得线程可以以一种无竞争的方式等待任意条件的发生。所谓无竞争就是,条件改变这个信号会发送到所有等待这个信号的线程。而不是说一个线程接受到这个消息而其它线程就接收不到了。
条件变量的用法
创建和注销:
条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用
PTHREAD_COND_INITIALIZER
常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
动态方式调用
pthread_cond_init()
函数,定义如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
//成功返回0,失败返回错误码.
初始化一个条件变量。当参数cond_attr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cond_attr中的属性值来决定。调用 pthread_cond_init函数时,参数cattr为空指针等价于cond_attr中的属性为缺省属性,只是前者不需要cond_attr所占用的内存开销。这个函数返回时,条件变量被存放在参数cv指向的内存中。
可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。
阻塞在条件变量上 :pthread_cond_wait
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
返回值:函数成功返回0;任何其他返回值都表示错误
函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。
被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。
pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。
阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:
pthread_mutex_lock();
while (condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。
可总结如下:
使用pthread_cond_wait前要先加锁;
pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活;
pthread_cond_wait被激活后会再自动加锁;
解除在条件变量上的阻塞:pthread_cond_signal
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
函数被用来释放被阻塞在指定条件变量上的一个线程。
必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。
唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。
如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。
阻塞直到指定时间:pthread_cond_timedwait
#include <pthread.h>
#include <time.h>
int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mp, const structtimespec * abstime);
返回值:函数成功返回0;任何其他返回值都表示错误
函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
注意:pthread_cond_timedwait函数也是退出点。
超时时间参数是指一天中的某个时刻。使用举例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超时返回的错误码是ETIMEDOUT。
释放阻塞的所有线程:pthread_cond_broadcast
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。
由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。
释放条件变量:pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
释放条件变量。注意:条件变量占用的空间并未被释放。
唤醒丢失问题
在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。
唤醒丢失往往会在下面的情况下发生:
一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
以上内容参考:关于条件变量
有了以上的基础,做以下实验加深对条件变量的了解:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//互斥锁,用于配合条件变量的使用
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//条件变量
void *thread1(void*);
void *thread2(void*);
int i=1;
int main(int argc, char *argv[])
{
//定义两个线程t_a、t_b
pthread_t t_a;
pthread_t t_b;
//创建线程
pthread_create(&t_a, NULL, thread1, NULL);
pthread_create(&t_b, NULL, thread2, NULL);
//销毁线程
pthread_join(t_a, NULL);
pthread_join(t_b, NULL);
//销毁条件变量
pthread_mutex_destory(&mutex);
pthread_cond_destory(&cond);
exit(0);
}
void *thread1(void *junk)
{
for(i=1; i<=6; i++)
{
pthread_mutex_lock(&mutex);//上锁,若是thread1给mutex加锁的话,其他线程将在mutex上等待
printf("thread1: lock %d \n", _LINE_);
if(!(i%3))//此处是3和6
{
printf("thread1: signal 1 %d\n", _LINE_);
pthread_cond_signal(&cond);//解除在条件变量上的阻塞,发送信号,通知t_b进程
printf("thread1: signal 2 %d\n", _LINE_);
sleep(1);
}
pthread_mutex_unlock(&mutex);//解除互斥锁
printf("thread1: unlock %d\n\n", _LINE_);
sleep(1);
}
}
void *thread2(void *junk)
{
while(i < 6)
{
pthread_mutex_lock(&mutex);//上锁,若是thread2给mutex加锁的话,其他线程将在mutex上等待
printf("thread2: lock %d\n", _LINE_);
if(i%3)
{
printf("thread2: wait 1 %d\n", _LINE_);
pthread_cond_wait(&cond, &mutex);//解锁mutex,在cond这个条件变量上阻塞等待
printf("thread2: wait 2 %d\n", _LINE_);
}
pthread_mutex_unlock(&mutex);//解除互斥锁mutex
printf("thread2:unlock %d\n\n", _LINE_);
sleep(1);
}
}
[651km@localhost ~]$ gcc semaphore5.c -pthread
[651km@localhost ~]$ ./a.out
thread1: lock 28
thread1: unlock 37
thread2: lock 47
thread2: wait 1 50
thread1: lock 28
thread1: unlock 37
thread1: lock 28
thread1: signal 1 31
thread1: signal 2 33
thread1: unlock 37
thread2: wait 2 52
thread2:unlock 55
thread1: lock 28
thread1: unlock 37
thread2: lock 47
thread2: wait 1 50
thread1: lock 28
thread1: unlock 37
thread1: lock 28
thread1: signal 1 31
thread1: signal 2 33
thread1: unlock 37
thread2: wait 2 52
thread2:unlock 55
[651km@localhost ~]$
分析:
首先,thread1和thread2是并发执行的,从运行结果来看是thread1先对mutex上锁,是thread1首先抢到了CPU的使用权,此时i=1,不会执行if语句内的代码段,然后解锁,sleep 1s。
接着thread2开始执行,thread2对mutex加锁,if语句成立,执行pthread_cond_wait(&cond, &mutex);对mutex解锁,在cond上等待。
thread1占据CPU使用权,thread1对mutex加锁,然后if语句不成立,对mutex解锁,sleep 1s;
接着thread1开始执行,此时i=3,thread1对mutex加锁,然后执行if语句,解除了在条件变量cond上阻塞的线程,此时thread2对mutex加锁,从阻塞处继续向下执行,对mutex解锁。
此后情况类似。