Linux之条件变量

条件变量

条件变量(cond)使在多线程程序中用来实现“等待--->唤醒”逻辑常用的方法,是进程间同步的一种机制。条件变量用来阻塞一个线程,直到条件满足被触发为止,通常情况下条件变量和互斥量同时使用。一般条件变量有两个状态:(1)一个/多个线程为等待“条件变量的条件成立“而挂起;(2)另一个线程在“条件变量条件成立时”通知其他线程。

为什么条件变量总是和互斥锁结合使用?

这其实有两方面的原因:

(1)互斥锁可以表示的状态的太少了,可以利用条件变量来增加有限的状态。

(2)条件变量虽然是线程同步的重要方法,但仅仅依靠条件变量是没有办法完成完成线程同步的工作的。

现在提出一个问题:

有两个线程,贡献一个全局变量count,count的初始值为0。这两个线程的任务是:线程1负责将count的的数值加到10,而线程而负责在线程1将count加到10之后将count输出后清零,这交替循环。


 
 
  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <sys/types.h>
  4. int count= 0;
  5. pthread_mutex_t myMutex=PTHREAD_MUTEX_INITIALIZER;
  6. pthread_cond_t myCond=PTHREAD_COND_INITIALIZER;
  7. void* threadHandle1(void* argv)
  8. {
  9. while( 1)
  10. {
  11. pthread_mutex_lock(&myMutex);
  12. ++count;
  13. pthread_mutex_unlock(&myMutex);
  14. //留给其他线程足够的时间争用锁
  15. sleep( 1);
  16. }
  17. }
  18. void* threadHandle2(void* argv)
  19. {
  20. while( 1)
  21. {
  22. //为了保证在线程进入临界区是,count的数值不会被修变。
  23. if(count== 10)
  24. {
  25. pthread_mutex_lock(&myMutex);
  26. if(count== 10)
  27. {
  28. printf( "%d\n",count);
  29. count= 0;
  30. }
  31. pthread_mutex_unlock(&myMutex);
  32. }
  33. printf( "%d\n",count);
  34. sleep( 1);
  35. }
  36. }
  37. int main()
  38. {
  39. pthread_t pid[ 2];
  40. pthread_create(&pid[ 0], NULL,threadHandle1, NULL);
  41. pthread_create(&pid[ 1], NULL,threadHandle2, NULL);
  42. pthread_join(pid[ 0], NULL);
  43. pthread_join(pid[ 1], NULL);
  44. return 0;
  45. }

虽然只是简单的两个线程对加法的运算,但线程1和线程2需要不停的交换锁的控制权,这样无疑就会给系统带来一些不必要的压力,原因是互斥锁只有两个状态(锁和不锁),而通过条件变量就会可以改进互斥锁在这一面的不足。


 
 
  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <sys/types.h>
  4. int count= 0;
  5. pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
  6. pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
  7. void* threadHandle1(void* argv)
  8. {
  9. while( 1)
  10. {
  11. pthread_mutex_lock(&mutex);
  12. ++count;
  13. printf( "thread1(mutex):count=%d\n",count);
  14. pthread_mutex_unlock(&mutex);
  15. pthread_mutex_lock(&mutex);
  16. if(count== 5)
  17. {
  18. if(pthread_cond_signal(&cond)== 0)
  19. {
  20. printf( "thread1:(count=5)signal\n");
  21. }
  22. }
  23. if(count>= 10)
  24. {
  25. if(pthread_cond_signal(&cond)== 0)
  26. {
  27. printf( "thread1:(count=10)signal\n");
  28. }
  29. }
  30. pthread_mutex_unlock(&mutex);
  31. printf( "thread1:(cond)unlock\n");
  32. sleep( 1);
  33. }
  34. }
  35. void* threadHandle2(void* argv)
  36. {
  37. while( 1)
  38. {
  39. pthread_mutex_lock(&mutex);
  40. while(count< 10)
  41. {
  42. //为什么使用while?
  43. //防止signal唤醒的时机不对。
  44. printf( "thread2(while):count=%d\n",count);
  45. //在函数返回之前将锁打开,在函数返回之后将锁关闭。
  46. pthread_cond_wait(&cond,&mutex);
  47. printf( "condWait\n");
  48. }
  49. if(count>= 10)
  50. {
  51. printf( "thread2(if):count=%d\n",count);
  52. count= 0;
  53. }
  54. pthread_mutex_unlock(&mutex);
  55. printf( "mutexUnlock\n");
  56. }
  57. }
  58. int main()
  59. {
  60. pthread_t pid[ 2];
  61. pthread_create(&pid[ 0], NULL,threadHandle1, NULL);
  62. sleep( 1);
  63. pthread_create(&pid[ 1], NULL,threadHandle2, NULL);
  64. pthread_join(pid[ 0], NULL);
  65. pthread_join(pid[ 1], NULL);
  66. return 0;
  67. }

 

代码解析:

pthread_cond_wait(&cond,&mutex);
 
 

该函数有三个作用:

(1)阻塞线程。

(2)将互斥锁解锁,并等待其他线程将其唤醒。(1)(2)为原子操作。

(3)在其他线程将其唤醒之后,将解锁的互斥锁重新加锁。

这里有两个问题:

(1)为什么要对线程2中的条件变量的部分加锁?

(2)在条件变量判断的时候为什么不用if而要使用while?

为什么要对线程2中的条件变量的部分加锁?

如果不加锁,在线程判断时假设这样一种情况:当线程1将count的数值加到9的时候,线程2去判断count的值,此时count的值还为9,那么线程2就会进入while循环中,等待线程1的条件成立,将自己唤醒。但就这这个时候,线程1还没有执行pthread_cond_wait时,线程1将count的值修改为10,并发送了signal信号,试图唤醒线程2。而线程2还没有执行wait所以并不会接收到这个信号,之后执行wait,而继续等待线程1的信号,但线程1会任务,自己已经将唤醒的信号发送了,这样就存在问题。

所以,需要在条件变量进行判断时,将变量锁住,让其他线程不能修改此变量,这样就可以保证在判断的时候条件的变量的值是正确的。即互斥锁的作用不是为了保护条件变量,而是为了保护条件判断时共享变量的值不会被修改。

在条件变量判断的时候为什么不用if而要使用while?

这个主要是为了防止其他线程在条件变量的条件还不成立的情况下,将睡眠中的线程错误的唤醒。

就像刚才的程序中的情况:我们的想法是在线程1将count的结果加到10时,将线程2唤醒,但线程1却在count等于5时将线程2唤醒,如果这里使用if就会出现问题。即程序不能保证signal线程将wait线程唤醒的时机时正确的,所以需要多重判断,就需要使用while,而不是使用if。

signal唤醒线程的时机

pthread_cond_signal(&cond);
 
 

通过上面的代码的结果分析,可以看出pthread_cond_signal的功能只是唤醒一个被条件变量阻塞的线程,但该函数不会修改锁的状态。而pthread_cond_wait会修改互斥锁的状态。

这里存在这样一个问题:(1)先解锁,再唤醒;(2)先唤醒,再解锁。因为wait再被唤醒会会有加锁操作。

(1)先解锁互斥锁,再唤醒睡眠的线程。

优点:减少了线程再内核态了用户态切换的次数,减少了资源的消耗。因为唤醒线程和解锁,都是需要再内核态完成的,而先解锁,再唤醒,内核会一次将这两个操作完成,这样就减少了用户态和内核态切换的次数,从而节省了资源。

缺点:如果此时存在一个低优先级的线程在等待锁,那么一旦锁被释放,那么这个锁就会被低优先级的线程争抢去,而不会被wait的线程得到,导致wait线程阻塞,无法返回。

(2)先唤醒睡眠的线程,再解锁互斥锁。

优点:唤醒后的线程在等待为该互斥锁加锁,一旦锁被释放,wait线程就会立即加锁,而不会发生上述,锁被抢占额度情况。

缺点:会增加用户态到内核态切换的次数,增加资源的消耗。

虽然在语法这两个都可以,但一般在程序使用先唤醒,再解锁的方式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值