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

11 篇文章 0 订阅
线程间的同步还有一个情况:进程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继续等待。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值