Linux线程同步-----条件变量

来源:互联网 发布:小米平板2怎么连接网络 编辑:程序博客网 时间:2024/04/30 13:51
线程间的同步还有一个情况:进程A 需要等待一个条件成立,才执行,当条件不成立时就阻塞等待 ;进程B 需要设置条件,当条件成立时,唤醒进程A.这里我们就可以用到条件变量。
条件变量变量也是出自POSIX线程标准,另一种线程同步机制,主要用来等待某个条件的发生,然后进行相应的操作,这样可以消除多线程间的竞争。每个条件变量总是和一个互斥量相关联,条件本身是由互斥量保护的,线程在改变条件状态之间必须要锁住互斥量。互斥量是用于上锁,条件变量用于等待。
1. 条件变量的初始化和销毁
#include <pthread.h>int pthread_cond_destroy(pthread_cond_t *cond);int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//成功返回0,失败返回错误码
Condition Variable 与Mutex 有类似的初始化函数pthread_cond_init, 当attr 为NULL 就相当于PTHREAD_COND_INITIALIZER
2. 条件变量的相关操作函数
#include <pthread.h>int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,                            const struct timespec *restrict abstime);int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);int pthread_cond_broadcast(pthread_cond_t *cond);int pthread_cond_signal(pthread_cond_t *cond); 
Condtion Variable 总是搭配Mutex 使用,超时条件变量的作用是为了防止某个线程因为异常终止,没有及时释放锁,超时会导致锁因为超时而释放。
3. 条件变量的使用实例
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>#include <errno.h>#include <sys/time.h>static int count = 0;pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;pthread_mutex_t g_cond_lock = PTHREAD_MUTEX_INITIALIZER;void *notify_thx_func(void *arg){    while (1)    {        sleep(5);        pthread_mutex_lock(&g_cond_lock);        ++count;        pthread_mutex_unlock(&g_cond_lock);        printf("please receive data ...\r\n");        pthread_cond_signal(&g_cond);    }}void *recv_thx_func(void *arg){    while (1)    {        pthread_mutex_lock(&g_cond_lock);        while (count <= 0)        {            pthread_cond_wait(&g_cond, &g_cond_lock);        }        printf("I got the data ...\r\n");        count--;        pthread_mutex_unlock(&g_cond_lock);    }}int main(int argc, char **argv){    pthread_t tid1, tid2;    pthread_create(&tid1, NULL, notify_thx_func, NULL);    pthread_create(&tid2, NULL, recv_thx_func, NULL);    pthread_join(tid1, NULL);    pthread_join(tid2, NULL);    return 0;}


4. 关于虚假唤醒 (Spurious wakeups)
互斥器和条件变量用法如下:
pthread_mutex_lock(&lock);while (condition_is_false) {    pthread_cond_wait(&cond, &lock);}
上面那个while能换成if吗?答案是不能,否则会导致spurious wakeup虚假唤醒。因为不仅要在pthread_cond_wait前要检查条件是否成立,在pthread_cond_wait之后也要检查。因为pthread_cond_wait不仅能被pthread_cond_signal/pthread_cond_broadcast唤醒,而且还会被其它信号唤醒,后者就是虚假唤醒。
考虑一个生产者消费者队列中的三个线程。
  1. 线程A正等待从线程安全的队列中读取一个元素。
  2. 线程B向队列中添加一个元素。在对队列解锁后,但在发出条件信号之前,发生了上下文切换。
  3. 线程C向队列中添加一个元素,并成功发出条件信号。
  4. 线程A被唤醒,并处理了上面两个元素。它接着等待被唤醒。
  5. 线程B恢复现场,并发出条件信号。
  6. 这时线程A被唤醒,但立即又进入睡眠,因为队列为空。
这个例子很好的解释了虚假唤醒,虽然没有什么副作用,但是却有可能浪费CPU时间。
既然你已经始终需要检查谓词在一个循环,这没有什么区别。 如果基础条件变量可以有其他类型的虚假唤醒。
linux的pthread_cond_wait是用futex系统调用,这个是慢速系统调用,看过apue知道任何慢速系统调用被信号打断的时候会返回 -1,并且把errno置为EINTR,如果慢速系统调用的重启功能被关闭,需要在调用该系统调用的地方手动重启它,像下面这样:
while (1) {    int ret = syscall();    if (ret < 0 && errno == EINTR)        continue;    else        break;}

但是futex不能这么用,因为futex结束后到再次重启这个过程有个时间窗,在这个窗口内可能发生了pthread_cond_signal/phread_cond_broadcast,如果发生这种情况,再进行pthread_cond_wait的时候就错过了一次条件变量的变化,就会无限等待下去。但是如果不像上面那样写又无法重启futex系统调用,咋整呢?这就回到了上面检查布尔条件的时候为什么用while而不用if。用while不会因为虚假唤醒而错过phread_cond_signal/pthread_cond_broadcast,而且在通过判断while条件不成立检测出此次唤醒为虚假唤醒并继续调用futex继续等待。
0 0
原创粉丝点击