AnSwErYWJ's Blog

线程同步机制条件变量的使用与思考

字数统计: 976阅读时长: 20 min
2017/12/15

条件变量是Linux线程同步的一种机制,与互斥量一起使用时,允许线程以无竞争的方式等待特定条件的发生

[TOC]

关键函数

初始化与注销

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <pthread.h>

// 静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 动态初始化
int pthread_cond_init(thread_cond_t *cond,
const pthread_condattr_t *attr);

// 反初始化,即注销
int pthread_cond_destroy(pthread_cond_t *cond);

返回值: 若成功,返回0;否则,返回错误编码

注意:

  • 只有在没有线程在该条件变量上等待时,才可以注销条件变量,否则会返回EBUSY
  • Linux在实现条件变量时,并没有为条件变量分配资源,所以在注销一个条件变量时,只需要注意该变量是否仍有等待线程即可

线程等待

1
2
3
4
5
6
7
8
9
10
#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);

int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);

返回值: 若成功,返回0;否则,返回错误编码

执行过程如下:

  1. 调用者把锁住的互斥量传给函数,然后函数自动把调用线程放到等待条件的线程列表上
  2. 对互斥量进行解锁,线程挂起进入等待(不占用CPU时间) 
  3. 函数被唤醒返回时,会自动对互斥量进行加锁

pthread_cond_timedwait只是多了一个等待超时时间,通过timespec指定,超时返回错误ETIMEDOUT

线程唤醒

1
2
3
4
5
6
7
#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

返回值: 若成功,返回0;否则,返回错误编码
  • pthread_cond_signal至少能唤醒一个等待该条件的线程
  • pthread_cond_broadcast则能唤醒等待该条件的所有线程

    需要注意的是,一定要在改变条件状态以后再给线程发信号

示例

示例代码可参考我的github,由于篇幅原因,不在此贴出

一些思考

条件变量实质是什么

条件变量实质是利用线程间共享的全局变量进行同步的一种机制

互斥量保护的是什么

示例中的相关代码

1
2
3
4
5
6
7
8
pthread_mutex_lock(&(test->mut));

while (test->condition == 0)
{
pthread_cond_wait(&(test->cond), &(test->mut));
}

pthread_mutex_unlock(&(test->mut));

互斥量是用来保护条件test->condition在读取时,它的值不被其它线程修改,如果条件成立,则此线程进入等待条件的线程队列,对互斥量进行解锁并开始等待

为什么用while来判断条件

如上面的代码所示,使用while对条件进行判断的原因如下:

  1. 若先解锁互斥量,再唤醒等待线程,则条件可能被其它线程更改,使得等待条件再次成立,需要继续等待
  2. pthread_cond_wait可能存在意外返回的情况,则此时条件并没有被更改,需要继续等待。

    造成意外返回的原因是Linux中带阻塞功能的系统调用都会在进程收到signal后返回

先唤醒线程还是先解锁

示例代码:

  1. 情况一:先唤醒

    1
    2
    3
    4
    pthread_mutex_lock(&(test->mut));
    test->condition = 1
    pthread_cond_signal(&(test->cond));
    pthread_mutex_unlock(&(test->mut));
  2. 情况二:先解锁

    1
    2
    3
    4
    pthread_mutex_lock(&(test->mut));
    test->condition = 1
    pthread_mutex_unlock(&(test->mut));
    pthread_cond_signal(&(test->cond));

    两种情况各有缺点:

  • 情况一在唤醒等待线程后,再解锁,使得等待线程在被唤醒后试图对互斥量进行加锁时,互斥量还未解锁,则线程又进入睡眠,待互斥量解锁成功后,再次被唤醒并对互斥量加锁,这样就会发生两次上下文切换,影响性能
  • 情况二在唤醒等待线程前先解锁,使得其它线程可能先于等待线程获取互斥量,并对条件进行更改,使得条件变量失去作用

Reference

CATALOG
  1. 1. 关键函数
    1. 1.1. 初始化与注销
    2. 1.2. 线程等待
    3. 1.3. 线程唤醒
  2. 2. 示例
  3. 3. 一些思考
    1. 3.1. 条件变量实质是什么
    2. 3.2. 互斥量保护的是什么
    3. 3.3. 为什么用while来判断条件
    4. 3.4. 先唤醒线程还是先解锁
  4. 4. Reference