ZZ:http://m.blog.youkuaiyun.com/blog/yeh201111/8189517
对线程中条件变量的理解:
最近在学线程,学到这个条件变量的时候,感觉很难理解。。。查阅了大量的资料,终于有所突破,现在将一些体会写下来:
不得不提的是,条件变量必须牵涉到互斥锁,这个具体为什么,在我转载的另一篇文章中介绍到,有兴趣的可以了解一下。。。
首先,先简单的介绍一下条件变量,条件变量是用来通知共享数据状态信息的。可以使用条件变量来通知队列已空,或非空。为什么在线程中要引用条件变量呢?我在这里举个例子:线程A用来向共享数据-队列写入数据,而线程B用来读取队列中的数据。当然了,这个地方共享数据需要用一个互斥量保护。在线程B读取之前,需要锁住互斥量,然后判断队列是否为空,如果非空,直接读取然后返回就OK了;如果是空呢?那么线程B就需要阻塞,直到线程A向队列中写入数据使队列非空,如果这样的话,线程B的生命周期就没有什么意义了,他用他的一生来阻塞等待。。。为什么?因为线程B在判断之前,已经锁住了互斥量,这个时候线程A哪怕有很多的数据要写入队列,但是,他没有竞争到这个锁,就不得不等待线程B释放这个锁。。。就相当于B拿着大门的钥匙在屋里边等着A回家一样。这样就悲剧了。。。不是吗?所以,线程引入了条件变量这个概念。。。
先打一个比方,有两个人A,B有两个保险柜a,b(a保险柜属于A,b保险柜属于B),意外的是,这两个保险柜公用一个钥匙,而且A,B两人只有一把钥匙。。。保险柜里面各自锁了自己的手机。现在有这样的一件事:A想要给B发一条短信,而B呢?B想要查看A发给自己的短信内容。。。这两件事都离不开手机,所以他们不得不去竞争这唯一的钥匙。。。结果又两种:1,A抢到了这把钥匙,打开a保险柜,取出手机给B了一个短信,然后将钥匙给刚才没有抢到钥匙的B,B打开b保险柜,取出手机看到了A的短信;2,这第二种结果就有意思了,假设B抢到了这把钥匙,打开b保险柜,取出手机,但是却没有看到短信,B怎么办呢?很明智的,B重新锁住了保险柜,然后将钥匙又给了A,对A说,你先用吧,我等着你。。。A打开a保险柜,给B的手机发了一个短信,然后把钥匙给了B,对B说,我忙完了,你不用等待了,你继续。。。然后呢,B就打开保险柜取出手机看到了短信。。。。
很温馨的第二种结果,哈哈,其实呢,这就是条件变量的机制(我个人理解,欢迎高手提出错误)。
两个人相当于两个线程,而唯一的钥匙呢,就是互斥锁吧。。。第二种结果的整个过程在pthread_cond_wait(),pthread_cond_signal()的实现过程中表现的淋漓尽致。。。这个函数的作用会阻塞需要读取队列的线程B,但是呢,在阻塞之前,条件变量操作将会解锁互斥量(这时线程A在等待线程B释放这个锁,所以此时线程A会竞争到锁然后向队列中写入数据,写入之后,线程A会向线程B发送信号,唤醒线程B,同时解锁这个互斥量),而在线程B被唤醒时,会再次锁住互斥量。。。这里有一段代码,可以帮助你更好的理解条件变量的机制:
#include"stdlib.h"
#include"error.h"
#include"string.h"
#include"errno.h"
#include"pthread.h"
#include"time.h"
typedef struct my_struct_tag
{
pthread_mutex_t mutex;
pthread_cond_t cond;
int value;
}my_struct_t;
my_struct_t data = {
PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};
int hibernation = 1;
void *wait_thread(void *arg)
{
int status;
sleep(hibernation);//刻意的想要实现第二种结果,即让main抢到这个锁
status = pthread_mutex_lock(&data.mutex);//锁住互斥量,
if(status)
{
fprintf(stderr, "Lock mutex error: %s\n", strerror(errno));
pthread_exit(NULL);
}
data.value = 1;//这个操作就是这个线程需要完成的任务
status = pthread_cond_signal(&data.cond);//完成任务后,唤醒等待cond条件变量的线程,注意,这个函数一次最多只能唤醒一个线程,(如果多个线程在等待,会按照 优先级进行线程排序’如果需要同时唤醒多个线程,需要用到pthread_cond_broadcast()函数。
{
fprintf(stderr, "Signal error: %s\n", strerror(errno));
pthread_exit(NULL);
}
status = pthread_mutex_unlock(&data.mutex);
if(status)
{
fprintf(stderr, "Unlock mutex error: %s\n", strerror(errno));
pthread_exit(NULL);
}
return NULL;
}
int main(int argc, char *argv[])
{
int status;
pthread_t wait_thread_id;
struct timespec timeout;
if(argc > 1)
{
hibernation = atoi(argv[1]);//默认值为1,你可以输入参数修改默认值
}
status = pthread_create(&wait_thread_id, NULL, wait_thread, NULL);//创建wait_thread线程
if(status)
{
fprintf(stderr, "Create thread error: %s\n", strerror(errno));
return -1;
}
timeout.tv_sec = time(NULL) + 2;
timeout.tv_nsec = 0;
status = pthread_mutex_lock(&data.mutex);
if(status)
{
fprintf(stderr, "Main thread_mutex_lock error: %s\n", strerror(errno));
return -1;
}
while(data.value == 0)
{
status = pthread_cond_timedwait(&data.cond, &data.mutex, &timeout);//这个是时间等待函数,在这个时间内等待;pthread_cond_wait()是不需要等待时间的,这个函数会首先解锁mutex,然后阻塞,等待线程wait_thread发送信号来唤醒他。在唤醒后,这个线程会重新锁着mutex这个互斥量。在这里还有一个问题,如果等到条件变量的等待被唤醒,他需要第一时间加锁互斥量,但是,发送信号的线程并没有释放互斥量,该怎么办呢?这个刚被唤醒的等待需要再次等待,这样就切换了两次环境。所以,尽量保证发信号时就解锁相应的互斥量。
if(status == ETIMEDOUT)
{
printf("Condition wait timed out.\n");
break;
}
else if(status != 0)
{
fprintf(stderr, "Wait on condition error: %s\n", strerror(errno));
exit(1);
}
}
if(data.value != 0)//如果value的值发生改变,就说明线程已经修改了value的值,并且发送了信号来唤醒等待cond条件变量的线程。
{
printf("Conditon was signaled.\n");
}
status = pthread_mutex_unlock(&data.mutex);
if(status != 0)
{
fprintf(stderr, "Pthread_mutex_unlock error: %s\n", strerror(errno));
exit(1);
}
return 0;
}