上一篇文章总结了POSIX信号量与互斥锁的内容,接下来学习条件变量的相关知识,主要有以下内容:条件变量、条件变量函数、条件变量使用规范以及使用条件变量解决生产者消费者问题。
1.条件变量
(1)当一个线程互斥地访问某个变量时,它可能发现在其他线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,直到其他线程将一个节点添加到队列中。这种情况就需要用到条件变量。
(2)再举一个例子:
1)假设有两个线程同时访问一个全局变量n,这个全局变量的初始值为0;
2)这个时候一个线程进入临界区—>对互斥量进行加锁操作—>访问变量n,但是该线程访问n有个前提条件就是n的值必须大于0。所以,该线程就要等待n大于0这个条件…
3)另外一个线程同样进入临界区—>修改n的变量,使得n>0—>通知等待线程—>退出临界区。
但是这里有一个问题,就是当一个线程进入临界区之后,其他线程就进入不了临界区了,那么第二个线程就没法改变n>0这个条件以满足第一个线程的运行条件。f
(3)针对该问题,解决办法就是使用条件变量。
2.条件变量函数
pthread_cond_init:初始化一个条件变量
pthread_cond_destroy:销毁一个条件变量
pthread_cond_wait:在一个条件之上等待
pthread_cond_signal:当条件满足的时候,向等待条件的第一个线程发起通知
pthread_cond_broadcast:向等待的所有线程发起通知
条件变量通常要跟互斥锁一起使用,当等待条件的时候对该互斥锁进行解锁操作,以便其它线程能够进入到临界区。
3.条件变量使用规范
(1)等待条件代码
ptherad_mutex_lock(&mutex);
while(条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
(2)给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
4.使用条件变量解决生产者消费者问题
使用条件变量改进生产者消费者问题与上一篇文章使用POSIX信号量和互斥锁实现的模式类型。
(1)首先,同样先定义一些全局变量:生产者和消费者个数,以及互斥锁和条件变量等:
…
pthread_mutex_t g_mutex;
pthread_cond_t g_cond;
int nready = 0; //表示当前仓库产品个数
…
(2)然后在main函数中首先初始化互斥锁和条件变量:
pthread_mutex_init(&g_mutex, NULL);
pthread_cond_init(&g_cond, NULL);
(3)接下来创建若干消费者线程和生产者线程并等待这些线程的退出:
//与使用POSIX信号量和互斥锁实现的模型一样
(4)再就是销毁互斥锁与条件变量:
pthread_mutex_destroy(&g_mutex);
pthread_cond_destroy(&g_cond);
(5)然后就是在线程入口函数中,按照条件变量的使用规则来使用条件变量:
//对于消费者线程入口函数consume来说
void *consume(void *arg)
{
int num = (int)arg;
while(1)
{
pthread_mutex_lock(&g_mutex);//上锁
while(nread == 0)//条件不满足时,就一直等待…
{
pthread_cond_wait(&g_cond, &g_mutex);
}
//跳出while循环就说明条件满足了,就可以消费产品了
–nready;
pthread_mutex_unlock(&g_mutex);//解锁
}
return NULL;
}
//对生产者线程入口函数produce来说类似
…
while(1)
{
pthread_mutex_lock(&g_mutex);
++nready;
pthread_cond_signal(&g_cond);//发起通知
pthread_mutex_unlock(&g_mutex);
}
综上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
#include <stdio.h> #include <unistd.h> #include <pthread.h> #define CONSUMERS 2 #define PRODUCERS 1 pthread_mutex_t myMutex; pthread_cond_t myCond; pthread_t myThreads[CONSUMERS+PRODUCERS]; int nready = 0; void *consume(void *arg) { int tid = (int)arg; while(1){ pthread_mutex_lock(&myMutex); while(nready == 0){ printf("%d begin wait a condition...\n", tid); pthread_cond_wait(&myCond, &myMutex); } printf("%d end wait a condition...\n", tid); printf("%d begin consume product...\n", tid); --nready; printf("%d end consume product...\n", tid); pthread_mutex_unlock(&myMutex); sleep(1); } return NULL; } void *produce(void *arg) { int tid = (int)arg; while(1){ pthread_mutex_lock(&myMutex); printf("%d begin produce product...\n", tid); ++nready; printf("%d end produce product...\n", tid); pthread_cond_signal(&myCond); printf("%d signal...\n", tid); pthread_mutex_unlock(&myMutex); sleep(1); } return NULL; } int main(void) { int i; pthread_mutex_init(&myMutex,NULL); pthread_cond_init(&myCond, NULL); for(i=0; i<CONSUMERS; ++i){ pthread_create(&myThreads[i], NULL, consume, (void*)i); } sleep(1); for(i=0;i<PRODUCERS; ++i){ pthread_create(&myThreads[i+CONSUMERS], NULL, produce, (void*)(i+CONSUMERS)); } for(i=0; i<CONSUMERS+PRODUCERS; ++i) pthread_join(myThreads[i],NULL); pthread_mutex_destroy(&myMutex); pthread_cond_destroy(&myCond); return 0; } |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[root@VM_198_209_centos 40_demon_thread_condvar]# ./producerConsumer 1 begin wait a condition... 0 begin wait a condition... 2 begin produce product... 2 end produce product... 2 signal... 1 end wait a condition... 1 begin consume product... 1 end consume product... 1 begin wait a condition... 2 begin produce product... 2 end produce product... 2 signal... 0 end wait a condition... 0 begin consume product... 0 end consume product... 2 begin produce product... 2 end produce product... 2 signal... 0 end wait a condition... 0 begin consume product... |
(6)最后我们再来分析一下消费者线程入口函数中的pthead_cond_wait函数做了哪些事情?
pthread_cond_wait(&g_cond, g_mutex);
1)首先对g_mutex进行解锁;
2)等待条件,直到有线程向它发起通知;
3)重新对g_mutex进行加锁。