linux c上说:使用条件变量主要包括两个动作:一个等待使用资源的线程等待“条件变量被设置为真”;
另一个线程在使用完资源后“设置条件为真”,这样就可以保证线程间的同步了。这样就存在一个关键问题,
就是要保证条件变量能被正确的修改,条件变量要受到特殊的保护,实际使用中互斥锁扮演者这样一个保护者
的角色。
由上面的一段话我们可以知道,互斥锁与条件变量是形影不离的好朋友。可是,他们到底是怎样合作的呢?
我们来看一段代码。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //初始化互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化条件变量
void *thread1(void *arg);
void *thread2(void *arg);
int i= 1; //全局变量
int main(void)
{
pthread_t t_a;
pthread_t t_b;
pthread_create(&t_a, NULL, thread2, NULL);
pthread_create(&t_b, NULL, thread1, NULL);
pthread_join(t_a, NULL); //等待t_a,t_b的结束
pthread_join(t_b, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *arg)
{
for (i=1; i <= 6; i++)
{
pthread_mutex_lock(&mutex); //设置锁
if (i % 3 == 0)
{
pthread_cond_signal(&cond); //激活cond指向的条件变量
}
printf("thread1 :%d\n",i);
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
}
void *thread2(void *arg)
{
while (i <= 6)
{
pthread_mutex_lock(&mutex); //设置锁
if (i % 3 != 0)
{
pthread_cond_wait(&cond, &mutex); //阻塞cond指向的条件变量,等待被信号唤醒
}
printf("thread2 :%d\n",i);
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
}
以上代码究竟是怎样执行的呢?
首先创建了t_a,t_b两个线程,分别调用thread2,thread1两个函数,假设先执行thread2函数,i=1,设置了一个
锁,1不是3的倍数,执行pthread_cond_wait函数,这个函数将设置的索解开,然后阻塞cond指向的条件变量,等待被信
号唤醒;接着执行pthread1函数,设置锁,判断出1不是3的倍数,执行printf语句,输出1,解锁。这时线程t_a还处于阻塞状
态,所以继续额执行线程t_b即函数pthread1,i++,设置锁,2依然不是3的倍数,再次printf输出2,解锁。继续i++,设置
锁,判断出3是3的倍数,这时执行phread_cond_signal函数,激活cond指向的条件变量,这时,一直被阻塞的线程t_a才
会开始运行,它会自己解开锁,并自己再次设置好锁,然后执行printf语句,输出3,解锁,同时,signal后的printf语句也
会同时运行,也就是说,thread1中也会输出3。后面的4,5, 6同理。所以现在我们来看看执行结果:
我们将这段代码稍作修改,就可以更加清楚的看到执行流程了。
#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 *arg); void *thread2(void *arg); int i=1; int main(void) { pthread_t t_a; pthread_t t_b; pthread_create(&t_a, NULL, thread1, NULL); //创建进程t_a pthread_create(&t_b, NULL, thread2, NULL); //创建进程t_b pthread_join(t_a, NULL); //等待进程t_a结束 pthread_join(t_b, NULL); //等待进程t_b结束 pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); exit(0); } void *thread1(void *arg) { for(i=1; i<=6; i++) { pthread_mutex_lock(&mutex); //设置锁 printf("thread1: lock %d\n", __LINE__); if(i%3 == 0) { printf("thread1:signal %d\n", __LINE__); pthread_cond_signal(&cond); //激活cond所指条件变量 sleep(1); } printf("thread1: %d\n",i); printf("thread1: unlock %d\n\n", __LINE__); pthread_mutex_unlock(&mutex); //解锁 sleep(1); } } void *thread2(void *junk) { while(i<6) { pthread_mutex_lock(&mutex); //设置锁 printf("thread2: lock %d\n", __LINE__); if(i%3 != 0) { printf("thread2: wait %d\n", __LINE__); pthread_cond_wait(&cond,&mutex); //解锁mutex,并等待cond改变 } printf("thread2:%d\n",i); printf("thread2: unlock %d\n\n", __LINE__); pthread_mutex_unlock(&mutex); //解锁 sleep(1); } }
我们在每次设置锁,解锁,阻塞,激活的时候都加了输出行语句,这样就可以很清楚的看到执行过程了,运行结果如下:
如果想要输出1,2,3,4,5,6该怎么办呢?
只需要给thread1加上else就好了。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //初始化互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化条件变量
void *thread1(void *arg);
void *thread2(void *arg);
int i= 1; //全局变量
int main(void)
{
pthread_t t_a;
pthread_t t_b;
pthread_create(&t_a, NULL, thread2, NULL);
pthread_create(&t_b, NULL, thread1, NULL);
pthread_join(t_a, NULL); //等待t_a,t_b的结束
pthread_join(t_b, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *arg)
{
for (i=1; i <=6; i++)
{
pthread_mutex_lock(&mutex); //设置锁
if (i % 3 == 0)
{
pthread_cond_signal(&cond); //激活cond指向的条件变量
}
else
{
printf("thread1 :%d\n",i);
}
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
}
void *thread2(void *arg)
{
while (i <= 6)
{
pthread_mutex_lock(&mutex); //设置锁
if (i % 3 != 0)
{
pthread_cond_wait(&cond, &mutex); //阻塞cond指向的条件变量,等待被信号唤醒
}
printf("thread2 :%d\n",i);
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
}
输出结果:
总结一下:
pthread_cond_signal 只发信号,内部不会解锁,在Linux 线程中,有两个队列,分别是cond_wait队列和
mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,
不会有性能的损耗。(pthread_cond_signal unlock后pthread_cond_wait才能上锁)
pthread_cond_wait 先解锁,等待,有信号来,上锁。
其实为什么要将条件变量与互斥锁结合使用,就是因为如果线程A与线程B操作同一临界区时,当线程A加锁后,线
程B就不能再次加锁了,那么直到线程A解锁之前,线程B的每次加锁都会被阻塞,所以它会一直在判断能不能加锁,这种
方式是比较消耗资源的,而条件变量的出现,就解决了这种问题。当我们设置了条件变量,它会一直处于阻塞状态,直到
条件被信号唤醒,才开始执行。这样就免去了线程B每次都要判断用不用加锁的麻烦,线程B只需要等待被信号唤醒就可
以了。此外,互斥锁还有一个缺点就是会有死锁的情况,即当两个线程分别使用了互斥锁1和互斥锁2,而它们又分别等
待对方的互斥锁解开,这样就造成了死锁。由此可见,互斥锁与条件变量的结合使用是很有用的。