线程控制--互斥锁与条件变量

来源:互联网 发布:形容程序员的搞笑图片 编辑:程序博客网 时间:2024/05/01 21:34




                              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_bpthread_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 unlockpthread_cond_wait才能上锁)


              pthread_cond_wait 先解锁,等待,有信号来,上锁



         其实为什么要将条件变量与互斥锁结合使用,就是因为如果线程A与线程B操作同一临界区时,当线程A加锁后,线

程B就不能再次加锁了,那么直到线程A解锁之前,线程B的每次加锁都会被阻塞,所以它会一直在判断能不能加锁,这种

方式是比较消耗资源的,而条件变量的出现,就解决了这种问题。当我们设置了条件变量,它会一直处于阻塞状态,直到

条件被信号唤醒,才开始执行。这样就免去了线程B每次都要判断用不用加锁的麻烦,线程B只需要等待被信号唤醒就可

以了。此外,互斥锁还有一个缺点就是会有死锁的情况,即当两个线程分别使用了互斥锁1和互斥锁2,而它们又分别等

待对方的互斥锁解开,这样就造成了死锁。由此可见,互斥锁与条件变量的结合使用是很有用的。