Linux线程同步之条件变量

简介

条件变量是pthread库中提供的一种线程同步的手段,功能类似于信号量。除了条件变量,pthread库中还提供了互斥锁(及其变种,例如读写锁)以及自旋锁用于线程同步,这两种手段主要用于critical区域的保护,避免多线程同时访问同一资源,例如全局变量等。如APUE中的描述,当条件变量与互斥锁一起使用时,允许线程以无竞争的方式等待特定的条件发生,因此条件变量,更像信号量,是一种线程间的通信手段。

API

初始化函数:
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);

该函数用于对pthread_cond_t类型的条件变量cond进行初始化。如果条件变量是静态的(如全局变量),也可使用PTHREAD_COND_INITIALIZER对其进行初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
等待函数:
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr);

两个等待函数的区别在于,第一个函数会一直等待直至cond可用,而第二个函数在等待指定的时间后就会返回。这里,我们注意到,两个函数都需要传入一个额外的mutex作为参数。在调用wait函数前,必须获取到mutex,否则会触发未定义行为。在调用wait后,程序将原子地释放掉mutex并阻塞在cond上
释放函数:
int pthread_cond_signal(pthread_cond_t *cond);
int_pthread_cond_broadcast(pthread_cond_t *cond);

两个函数的区别在于,第一个函数唤醒阻塞在cond上的至少一个线程,这里务必要再三注意,是唤醒至少一个线程,也就是说从POSIX规范角度,唤醒多个线程也是满足规范的。第二个函数将唤醒阻塞在cond上的所有线程。无论持有或未持有互斥锁,释放函数的线程均可调用释放函数唤醒等待线程。
在释放后,等待线程从等待函数中返回,并且原子地获取到了mutex锁。

讨论

由于释放线程可以在持有或不持有锁的情况下调用释放函数,因此存在两种释放策略:
(1) 对互斥量加锁(pthread_lock_mutex)
(2) 改变互斥量保护的条件
(3) 给等待条件的线程发信号(pthread_cond_broadcast)
(4) 对互斥量解锁(pthread_mutex_unlock)
或者
(1) 对互斥量加锁(pthread_lock_mutex)
(2) 改变互斥量保护的条件
(3) 对互斥量解锁(pthread_mutex_unlock)
(4) 给等待条件的线程发信号(pthread_cond_broadcast)
这实际上是APUE第11章的课后习题。按课后解答,这两种释放策略均是正确的,但均存在各自的不足。对于第一种情况,在调用pthread_cond_broadcast后,等待线程从阻塞状态进入ready状态,但由于释放线程仍然占有mutex,导致等待线程立即又阻塞在mutex上。对于第二种情况,释放线程在第(3)步以及第(4)步之间,互斥量保护的条件是有可能被其他线程改掉的。因此,在等待线程从pthread_cond_wait返回后,必须再次判断保护条件。

示例代码

pthread_cond_signal拥有互斥锁释放

#include <stdio.h>
#include <pthread.h>


pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
int available = 0;


void *task1_entry(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&qlock);
        while(available == 0)
        {
            pthread_cond_wait(&cond, &qlock);
            printf("task1 wake. available = %d\n", available);
        }
        available = 0;
        pthread_mutex_unlock(&qlock);
    }
}


void *task2_entry(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&qlock);
        while(available == 0)
        {
            pthread_cond_wait(&cond, &qlock);
            printf("task2 wake. available = %d\n", available);
        }
        available = 0;
        pthread_mutex_unlock(&qlock);
    }
}

int main(int argc, char *argv)
{
    pid_t tid1, tid2;

    pthread_create(&tid1, NULL, task1_entry, NULL);

    pthread_create(&tid2, NULL, task2_entry, NULL);

    while(1)
    {
        sleep(2);
        pthread_mutex_lock(&qlock);
        available = 1;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&qlock);
    }

    return 0;

}

在这段代码中,启动了两个线程等待条件变量,在main函数中每隔2s释放条件变量。控制台打印如下:
task1 wake. available = 1
task2 wake. available = 1
task1 wake. available = 1
task2 wake. available = 1
可以看到,两个线程轮流获取到条件变量。

pthread_cond_signal未拥有互斥锁释放

修改main函数中释放条件变量部分,在调用pthread_cond_signal前,先释放互斥锁,代码如下:

while(1)
{
    sleep(2);
    pthread_mutex_lock(&qlock);
    available = 1;
    pthread_mutex_unlock(&qlock);
    pthread_cond_signal(&cond);
}

打印如下:
task1 wake. available = 1
task2 wake. available = 1
task1 wake. available = 1
task2 wake. available = 1
可以看出依然是正常的。

pthread_cond_broadcast拥有互斥锁释放

将pthread_cond_signal修改为pthread_cond_broadcast,在拥有互斥锁时调用,代码如下:

while(1)
{
    sleep(2);
    pthread_mutex_lock(&qlock);
    available = 1;
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&qlock);
}

打印如下:
task1 wake. available = 1
task2 wake. available = 0
task1 wake. available = 1
task2 wake. available = 0
task1 wake. available = 1
task2 wake. available = 0
task1 wake. available = 1
task2 wake. available = 0
每次task1以及task2均会被唤醒,而在task2返回之后,实际上available变量已经被task1置为0。在这种情况下,如果在pthread_cond_wait返回后,如果不检查available,则会发生问题。

pthread_cond_broadcast未拥有互斥锁释放

将pthread_cond_broadcast挪到mutex unlock之后,情况与拥有互斥锁释放相同,不再赘述。

总结

就我测试的系统(Linux 2.6.18-194.el5)来看,存在以下特性:
(1) pthread_cond_signal仅会唤醒一个线程,多个同等优先级的线程等待时,谁先等待谁先唤醒。
(2) pthread_cond_wait后,需要重新判断条件,避免唤醒多个线程后,除第一个外的其他线程在唤醒后,实际条件已经被第一个线程修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值