一.概述
上一篇,介绍了互斥量。条件变量与互斥量不同,互斥量是防止多线程同时访问共享的互斥变量来保护临界区。条件变量是多线程间可以通过它来告知其他线程某个状态发生了改变,让等待在这个条件变量的线程继续执行。通俗一点来讲:设置一个条件变量让线程1等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,继续向下执行。
条件变量总是和互斥量一起使用,互斥量保护着条件变量,防止多个线程对条件变量产生竞争。等会写个小例子,看它们如何一起合作!
二.函数接口
1.初始化条件变量
1.1:宏常量初始化
1 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
1.2:函数初始化
1 #include 2 3 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
跟互斥量类似,cond是条件变量的结构指针,attr是条件变量属性的结构指针。
2.等待和通知条件变量
1 #include 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 2 3 int pthread_cond_destroy(pthread_cond_t *cond);
三.简单的例子
我们还是用上一篇互斥量的例子。单独使用互斥量时,有些线程要获取某个状态的成立,需要多次进出临界区,对互斥量频繁加锁解锁造成系统资源的浪费。下面结合条件变量来解决这个问题:
1 /**
2 * @file pthread_mutex.c
3 */
4
5 #include
6 #include
7 #include
8 #include
9 #include
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里面的条件为真,就继续休眠!!!
