线程同步方式---2 条件变量

自述

  上面的介绍的互斥量,典型的消费者-生产者模型有一个问题,就是每一次生产者函数都要 加锁-查看money-解锁 ,这么一整流程即使在money !=0 的情况下依然要经历这个流程,造成资源浪费。

这里引入条件变量,当main中直到money=0的时候,通知生产者直接进入互斥资源生产money。

  条件变量总是和互斥量结合在一起使用!

  这里对于 cond 与 mutex结合使用 在逼乎上有这么一个说法:

pthread_mutex_lock: 获得锁则锁定然后执行第二句话,否则阻塞等锁。
pthread_cond_wait :1.首先解锁相当于pthread_mutex_unlock。2.然后建立锁与条件变量的联系,3.等待唤醒,4.唤醒后第一件事情是上锁相当于pthread_mutex_lock,然后执行下一句话。 作者:枫亦 链接:https://www.zhihu.com/question/24116967/answer/108470262 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  因为你在调用 if(cond) pthread_cond_wait 之前总是要lock住cond的(为了防止Race Condition)那么调用wait之后,必须需要执行一个unlock,然后的等待signal通知。

  唤醒之后的第一件事就是 lock 住condition。

  pthread_cond_wait 正是将这两个操作封装到了一起。不需要我们每次都在前后加锁手动控制了。所以它所做的事情可以概括成上面所说的三步;

条件变量是多线程间可以通过它来告知其他线程某个状态发生了改变,让等待在这个条件变量的线程继续执行。通俗一点来讲:设置一个条件变量让线程1等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,继续向下执行。

条件变量总是和互斥量一起使用,互斥量保护着条件变量,防止多个线程对条件变量产生竞争。等会写个小例子,看它们如何一起合作!

二.函数接口                                            

1.初始化条件变量

1.1:宏常量初始化

1 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

1.2:函数初始化

1 #include <pthread.h>
2 
3 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

跟互斥量类似,cond是条件变量的结构指针,attr是条件变量属性的结构指针。

2.等待和通知条件变量

复制代码
1 #include <pthread.h>
2 
3 int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
4 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
5 
6 int pthread_cond_broadcast(pthread_cond_t *cond);
7 int pthread_cond_signal(pthread_cond_t *cond);
复制代码

等待函数里面,要传入一个互斥量。pthread_cond_timewait()可以指定一个时间来等待,如果规定的时间没有获得通知,就返回ETIMEDOUT错误。而pthread_cond_wait()会一直阻塞。

通知函数,pthread_cond_signal()至少唤醒一个等待的线程,pthread_cond_broadcast()会唤醒在该条件变量上所有线程。

3.销毁条件变量

1 #include <pthread.h>
2 
3 int pthread_cond_destroy(pthread_cond_t *cond);

三.简单的例子                                       

我们还是用上一篇互斥量的例子。单独使用互斥量时,有些线程要获取某个状态的成立,需要多次进出临界区,对互斥量频繁加锁解锁造成系统资源的浪费。下面结合条件变量来解决这个问题:

复制代码
  1 /**
  2  * @file pthread_mutex.c
  3  */
  4 
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <string.h>
  8 #include <unistd.h>
  9 #include <pthread.h>
 10 
 11 /* 定义互斥量 */
 12 pthread_mutex_t mtx;
 13 /* 互斥量属性 */
 14 pthread_mutexattr_t mtx_attr;
 15 /* 全局资源 */
 16 int money;
 17 
 18 /* 条件变量 */
 19 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 20 
 21 void err_exit(const char *err_msg)
 22 {
 23     printf("error:%s\n", err_msg);
 24     exit(1);
 25 }
 26 
 27 /* 线程函数 */
 28 void *thread_fun(void *arg)
 29 {
 30     while (1)
 31     {
 32         /* 加锁 */
 33         pthread_mutex_lock(&mtx);
 34 
 35         /* 条件变量 */
 36         while (money > 0)
 37         {
 38             printf("子线程坐等money等于0...\n");
 39             pthread_cond_wait(&cond, &mtx);
 40         }
 41 
 42         printf("子线程进入临界区查看money\n");
 43         if (money == 0)
 44         {
 45             money += 200;
 46             printf("子线程:money = %d\n", money);
 47         }
 48 
 49         /* 解锁 */
 50         pthread_mutex_unlock(&mtx);
 51 
 52         sleep(1);
 53     }
 54 
 55     return NULL;
 56 }
 57 
 58 int main(void)
 59 {
 60     pthread_t tid;
 61 
 62     /* 初始化互斥量属性 */
 63     if (pthread_mutexattr_init(&mtx_attr) == -1)
 64         err_exit("pthread_mutexattr_init()");
 65 
 66     /* 设置互斥量属性 */
 67     if (pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_NORMAL) == -1)
 68         err_exit("pthread_mutexattr_settype()");
 69 
 70     /* 初始化互斥量 */
 71     if (pthread_mutex_init(&mtx, &mtx_attr) == -1)
 72         err_exit("pthread_mutex_init()");
 73 
 74     /* 创建一个线程 */
 75     if (pthread_create(&tid, NULL, thread_fun, NULL)== -1)
 76         err_exit("pthread_create()");
 77 
 78     money = 1000;
 79     while (1)
 80     {
 81         /* 加锁 */
 82         pthread_mutex_lock(&mtx);
 83 
 84         if (money > 0)
 85         {
 86             money -= 100;
 87             printf("主线程:money = %d\n", money);
 88         }
 89 
 90         /* 解锁 */
 91         pthread_mutex_unlock(&mtx);
 92 
 93         /* 如果money = 1,就通知子线程 */
 94         if (money == 0)
 95         {
 96             printf("通知子线程\n");
 97             pthread_cond_signal(&cond);
 98         }
 99 
100         sleep(1);
101     }
102 
103     return 0;
104 }
复制代码

代码跟上一个例子几乎一样,就加了一个条件变量。编译运行:

可以看到第39行的等待条件变量触发后,子线程会一直等待,直到主线程通知它。这样子线程就不会频繁进入临界区,频繁加锁解锁。

四.深入知识                                             

1.等待函数里面要传入一个互斥量,这个互斥量会在这个函数调用时会发生如下变化:函数刚刚被调用时,会把这个互斥量解锁,然后让调用线程阻塞,解锁后其他线程才有机会获得这个锁。当某个线程调用通知函数时,这个函数收到通知后,又把互斥量加锁,然后继续向下操作临界区。可见这个设计是非常合理的!!!

2.条件变量的等待函数用while循环包围,本程序的第36行。原因:如果有多个线程都在等待这个条件变量关联的互斥量,当条件变量收到通知,它下一步就是要锁住这个互斥量,但在这个极小的时间差里面,其他线程抢先获取了这互斥量并进入临界区把某个状态改变了。此时这个条件变量应该继续判断别人刚刚抢先修改的状态,即继续执行while的判断。还有一个原因时防止虚假通知,收到虚假通知后,只要while里面的条件为真,就继续休眠!!!

posted on 2017-04-05 16:08  暴力的轮胎  阅读(432)  评论(0编辑  收藏  举报

导航