使用条件变量进行线程间的同步

什么是条件变量

先看一下APUE第三版对于条件变量的说明:

Condition variables are another synchronization mechanism available to threads. These synchronization objects provide a place for threads to rendezvous. When used with mutexes, condition variables allow threads to wait in a race-free way for arbitrary conditions to occur.
The condition itself is protected by a mutex. A thread must first lock the mutex to change the condition state. Other threads will not notice the change until they acquire the mutex, because the mutex must be locked to be able to evaluate the condition.

条件变量是另一种线程同步机制,它为线程间共享的对象提供了同步的方法。当条件变量配合互斥锁(Mutex)使用时,允许多个线程处在一种自由等待任意条件发生的状态。

条件变量自身由互斥锁(Mutex)保护。线程必须在修改条件状态之前先对其上锁,其他线程不会在获取锁之前被通知到其状态变化,因为只有获取到锁才可以计算条件。

条件变量的数据类型是pthread_cond_t,条件变量属性的类型是pthread_condattr_t,它们都包含在头文件<pthread.h>中。

初始化和销毁

条件变量使用之前必须初始化,有两种方法:

  1. 对于静态的条件变量,可以用PTHREAD_COND_INITIALIZER来初始化。
  2. 用pthread_cond_init初始化动态分配的条件变量,pthread_condattr_t是用来设定其属性的变量,具体使用会在后面提到。

需要释放条件变量时,使用pthread_cond_destroy即可。

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

使用条件变量进行线程间的同步

等待

#include <pthread.h>

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);

调用phread_cond_wait或pthread_cond_timewait(以下简称wait函数)可以使当前线程等待某一条件的发生。两者的区别在于后者可以指定等待时间。
调用wait函数时,系统使调用线程进入等待状态后释放锁(所以我们必须先加锁后调用wait函数)。在这一步操作中的检查条件和进入等待是原子操作,所以线程不会错过条件的变化。当wait函数返回时,mutex会再次被加锁

其中pthread_cond_timewait中用到的timespec结构定义如下:

struct timespec
{
    __time_t tv_sec;        /* Seconds.  */
    __syscall_slong_t tv_nsec;  /* Nanoseconds.  */
};

需要注意的是,timespec是一个绝对时间,所以在使用前我们需要先取得当前时间,再加上等待时间。例如下面这样:

#include <sys/time.h>
#include <stdlib.h>

void maketimeout(struct timespec *tsp, long minutes)
{
    struct timeval now;
    /* get the current time */
    gettimeofday(&now, NULL);
    tsp->tv_sec = now.tv_sec;
    tsp->tv_nsec = now.tv_usec * 1000; /* usec to nsec */
    /* add the offset to get timeout value */
    tsp->tv_sec += minutes * 60;
}

如果时间到了还没有等到条件变化,函数会对mutex重新加锁并返回一个ETIMEOUT的错误。

当wait函数返回成功时,需要重新检查条件,因为条件有可能已经被其他线程修改。

通知

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

当条件满足时,可以用这两个函数用来通知其他线程。

pthread_cond_signal会唤醒至少一个等待的线程,而pthread_cond_broadcast会唤醒所有等待的线程。必须注意的是:我们必须在状态发生变化之后再发送信号给其他线程

条件变量属性

条件变量的数据类型是pthread_cond_t,它主要有两种属性:

  1. 设置条件变量是否进程间共享。
  2. 调用pthread_cond_timewait时使用的计时方式,也就是使用clock_settime函数时的clockid_t类型参数。
#include <pthread.h>

int pthread_condattr_init(pthread_condattr_t *attr); 

int pthread_condattr_destroy(pthread_condattr_t *attr);

设置进程间共享属性:

int pthread_condattr_getpshared(const pthread_condattr_t * restrict attr, int *restrict pshared); 

int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);

设置时钟属性:

int pthread_condattr_getclock(const pthread_condattr_t * restrict attr, clockid_t *restrict clock_id); 

int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);

关于设置时钟属性的解释

pthread_cond_timewait函数用于在等待条件变量时提供超时功能,不过该函数的超时时间是一个绝对时间。默认使用系统时间,这意味着若修改系统时间,那么超时就不准确:有可能提前返回,也可能要几年才返回。这在某些情况下会导致bug,这时我们可以通过设置条件变量的时钟属性来避免这个问题。下面的例子展示了如何使用这个属性:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

int main()
{
    pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
    pthread_condattr_t attr;
    pthread_cond_t cond;
    pthread_condattr_init(&attr);
    pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
    pthread_cond_init(&cond, &attr);
    struct timespec tv;
    pthread_mutex_lock(&m);
    do
    {
        clock_gettime(CLOCK_MONOTONIC, &tv);
        tv.tv_sec += 1;
        pthread_cond_timedwait(&cond,&m,&tv);
        printf("heart beat\n");
    } while(1);
    
    pthread_mutex_unlock(&m);
    return 0;
}
关于clockid_t的两种常用类型:
  1. CLOCK_REALTIME :默认类型,它记录的时间是按照系统的时间计算的,也就是说在计算中有人调整了系统时间,它也会受到影响。
  2. CLOCK_MONOTONIC : monotonic time的字面意思是单调时间,实际上它指的是系统启动以后流逝的时间。它一定是单调递增的,即使有人调整了系统时间,它也不会受到影响。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容