线程同步——条件变量(代码片段)

Shemesz Shemesz     2022-12-09     433

关键词:

一、条件变量介绍

  条件变量是线程可用的一种同步机制。 条件变量给多个线程提供了一个回合的场所,条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。
  条件本身是由互斥量保护的。 线程在改变条件状态之前必须先锁住互斥量。其他线程在获得互斥量之前都不会察觉到这种改变,因为互斥量必须在锁定以后才能计算体条件。
  在使用条件变量之前,必须进行初始化。 由pthread_cond_t数据类型表示的条件变量可以通过两种方式初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但是如果条件变量是动态分配的,则需要使用pthread_cond_init函数对他进行初始化。

示例说明 :
假设进程里有两个线程一个主线程一个线程,程序要求执行流程如下

第一步,在主线程执行任务A;
第二步,在子线程执行任务B;
第三步,在主线程执行任务C;
第四步,程序退出

创建全局变量giWorkFlag用来线程之间通信;全局互斥体g_mutex用来保护g_iWorkFlag。

// 全局变量
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
int g_iWorkFlag = 0;

总体框架如下
在这里插入图片描述
运行步骤:

第1步:创建子线程任务
第2步:主线程执行工作A
第3步:执行完工作A后,用条件变量,通知子线程执行工作B
第4步:守候条件变量,等执行工作B…
第5步:得到主线程通知后,在此执行工作B
第6步:执行完工作B后,用条件变量,通知主线程执行工作C
第7步:执行完工作B,子线程退出
第8步:守候条件变量,等执行工作C…
第9步:主线程执行工作C
第10步:等待子线程退出(其实早已退出),退出主程序

二、运行代码

// 主线程
int main(void)

    printf ("%d MAIN: START. // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);

    // 第1步:创建子线程任务
    pthread_t tid;
    pthread_create(&tid, NULL, func_thread, NULL);

    // 第2步:主线程执行工作A
    printf ("%d MAIN: WORKING A... // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);
    sleep(2);   // 这是工作A
    printf ("%d MAIN: WORK A DONE. // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);

    // 第3步:执行完工作A后,用条件变量,通知子线程执行工作B
    pthread_mutex_lock(&g_mutex);
    g_iWorkFlag = 1; // 1=>B
    pthread_cond_signal(&g_cond);
    pthread_mutex_unlock(&g_mutex);
 
    // 第8步:守候条件变量,等执行工作C...
    pthread_mutex_lock(&g_mutex);
    while (g_iWorkFlag != 2)
        pthread_cond_wait(&g_cond, &g_mutex);
    pthread_mutex_unlock(&g_mutex);

    // 第9步:主线程执行工作C
    printf ("%d MAIN: WORKING C... // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);
    sleep(2);   // 这是工作C
    printf ("%d MAIN: WORK C DONE. // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);

    // 第10步:等待子线程退出(其实早已退出),退出主程序
    pthread_join(tid, NULL);
    printf ("%d MAIN: EXIT. // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);
    return 0;


// 子线程
static void *func_thread(void *arg)

    printf ("%d SUB_THREAD: ENTER... // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);

    // 第4步:守候条件变量,等执行工作B...
    pthread_mutex_lock(&g_mutex);
    while (g_iWorkFlag != 1)
        pthread_cond_wait(&g_cond, &g_mutex);
    pthread_mutex_unlock(&g_mutex);

    // 第5步:得到主线程通知后,在此执行工作B
    printf ("%d SUB_THREAD: WORKING B... // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);
    sleep(2); // 这是工作B
    printf ("%d SUB_THREAD: WORK B DONE. // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);
 
    // 第6步:执行完工作B后,用条件变量,通知主线程执行工作C
    pthread_mutex_lock(&g_mutex);
    g_iWorkFlag = 2; // 2=>C
    pthread_cond_signal(&g_cond);
    pthread_mutex_unlock(&g_mutex);

    // 第7步:执行完工作B,子线程退出
    printf ("%d SUB_THREAD: EXIT. // WORK_STEP=%d\\n", (int)time(NULL), g_iWorkFlag);
    return 0;

运行结果
在这里插入图片描述
上边的源码已经通过注释和打印描述得很清楚,这里不再追溯。只有pthread_cond_wait()比较难于理解,这里着重解释一下,即pthread_cond_wait()内部会执行如下2步:

第1步:解锁,阻塞着等待条件变量。
第2步:被条件变量唤醒,加锁,返回。

为了保护pthread_cond_wait()内部阻塞等待之前的“对g_cond的操作1”和阻塞等待之后的“对g_cond的操作2”,以及为了与pthread_cond_wait()内部的解锁与加锁对应上,所以需要在外部前边加锁,后边解锁。
在这里插入图片描述
注意事项:
为什么要用while(g_iWorkFlag != XXX)呢?这是因为pthread_cond_wait()可能被虚假唤醒(Spurious Wakeups),pthread_cond_signal()可能会唤醒多个线程上的条件变量;而条件变量本身不能精确指定流程的信息,所以需要通过一个全局变量g_iWorkFlag来指示流程细节状态。如果有的线程被唤醒,但流程状态不是自己所期待的,则通过while循环重新调用pthread_cond_wait()阻塞等待。而g_mutex既保护了条件变量,也保护了g_iWorkFlag,一举两得。

三、条件变量相关API

  • pthread_cond_init()函数 功能:初始化一个条件变量
  • pthread_cond_wait()函数 功能:阻塞等待一个条件变量
  • pthread_cond_timedwait()函数 功能:限时等待一个条件变量
  • pthread_cond_signal()函数 功能:唤醒至少一个阻塞在条件变量上的线程
  • pthread_cond_broadcast()函数 功能:唤醒全部阻塞在条件变量上的线程
  • pthread_cond_destroy()函数 功能:销毁一个条件变量
  • 以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。

pthread_cond_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。如:

pthread_cond_t cond; 变量cond只有两种取值1、0。

<1> 初始化一个条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);2:attr表条件变量属性,通常为默认值,传NULL即可

也可以使用静态初始化的方法,初始化条件变量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

<2> 阻塞等待一个条件变量

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

函数作用:阻塞等待条件变量cond(参1)满足
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
 1.2.两步为一个原子操作。
3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

<3> 限时等待一个条件变量

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);3: 参看man sem_timedwait函数,查看struct timespec结构体。

struct timespec 
time_t tv_sec; /* seconds */long   tv_nsec; /* nanosecondes*/ 纳秒



形参abstime:绝对时间。

如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。

struct timespec t = 1, 0;

pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 19701100:00:01(早已经过去) 

正确用法:

time_t cur = time(NULL); 获取当前时间。

struct timespec t; 定义timespec 结构体变量t

t.tv_sec = cur+1; 定时1pthread_cond_timedwait (&cond, &mutex, &t); 传参 参APUE.11.6线程同步条件变量小节

<4> 唤醒至少一个阻塞在条件变量上的线程

int pthread_cond_signal(pthread_cond_t *cond); 

<5> 唤醒全部阻塞在条件变量上的线程

 int pthread_cond_broadcast(pthread_cond_t *cond); 

<6> 销毁一个条件变量

int pthread_cond_destroy(pthread_cond_t *cond); 

线程同步——条件变量(代码片段)

...码三、条件变量相关API一、条件变量介绍  条件变量是线程可用的一种同步机制。条件变量给多个线程提供了一个回合的场所,条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。  条件本... 查看详情

线程同步图解-条件变量(代码片段)

线程同步问题广泛应用于多种场景,特别是与网络数据收发等耗时操作有关的场景。线程的操作往往比较抽象,线程大多运行在程序的后台,无法直观的查看其运行状态,因此,本文以图解的形式,为读者讲述线程同步的原理,... 查看详情

线程同步(互斥锁读写锁条件变量信号量)(代码片段)

参考:(四十三)线程——线程同步(互斥锁、读写锁、条件变量、信号量)作者:FadeFarAway发布时间:2017-01-1721:25:28网址:https://blog.csdn.net/FadeFarAway/article/details/54584286目录一、引入二、互斥量与 查看详情

[c++11多线程同步]---条件变量(代码片段)

...的时候,它使用std::unique_lock(通过std::mutex)来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的std::condition_variable对象上调用了notification函数来唤醒当前线程。st 查看详情

[c++11多线程同步]---条件变量(代码片段)

...的时候,它使用std::unique_lock(通过std::mutex)来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的std::condition_variable对象上调用了notification函数来唤醒当前线程。st 查看详情

线程同步与互斥详解(代码片段)

线程同步与互斥文章目录线程同步与互斥线程互斥进程线程间的互斥相关背景概念互斥量的接口互斥量实现原理可重入和线程安全常见的线程不安全的情况常见的线程安全情况常见锁概念死锁死锁四个必要条件避免死锁避免死锁... 查看详情

linux多线程编程与同步实例(基于条件变量)(代码片段)

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。C语言编... 查看详情

线程同步之经典模型——生产者消费者模型(代码片段)

生产者-消费者模型1、条件变量条件变量是线程之间的一种通知机制,当某个共享数据达到某个条件时,唤醒等待这个条件的线程。通过线程间共享的全局变量进行同步一个线程等待“条件变量条件成立”而阻塞另一个线... 查看详情

[c++11多线程同步]---条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spuriouswakeup)(代码片段)

1条件变量的信号丢失1.1条件变量的信号丢失场景重现拿生产者和消费者模型举例,看一段示例代码:#include<iostream>#include<vector>#include<mutex>#include<condition_variable>#include<thread>#include<unistd.h>std::m 查看详情

[c++11多线程同步]---条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spuriouswakeup)(代码片段)

1条件变量的信号丢失1.1条件变量的信号丢失场景重现拿生产者和消费者模型举例,看一段示例代码:#include<iostream>#include<vector>#include<mutex>#include<condition_variable>#include<thread>#include<unis 查看详情

linxu多线程(进程与线程区别,互斥同步)(代码片段)

互斥同步线程线程概念线程优点线程缺点线程异常Linux进程VS线程概念关系线程控制创建线程线程ID及进程地址空间布局线程终止线程等待线程分离互斥背景知识互斥量mutex互斥条件互斥量的接口实例运用(售票系统)互斥... 查看详情

linux多线程——互斥和同步(代码片段)

目录一.线程互斥    1.1相关概念    1.2互斥量mutex    1.3互斥量的接口    1.4总结    1.5互斥锁实现原理(锁的原理)二.可重入函数和线程安全    2.1概念三.死锁         3.1概念    3.2死锁的必要条件        3.... 查看详情

linux线程同步之条件变量pthread_cond_t(代码片段)

一直以来都天真的认为线程间同步的方法只有信号量,互斥量,邮箱,消息队列,知道最近开始研究一些Linux方面的代码才发现自己是多么的很傻很天真。在Linux中还存在这一种叫做条件变量的东西。必须承认我在... 查看详情

linux线程同步之条件变量pthread_cond_t(代码片段)

一直以来都天真的认为线程间同步的方法只有信号量,互斥量,邮箱,消息队列,知道最近开始研究一些Linux方面的代码才发现自己是多么的很傻很天真。在Linux中还存在这一种叫做条件变量的东西。必须承认我在... 查看详情

linux篇第十四篇——多线程(线程同步和互斥+线程安全+条件变量)(代码片段)

⭐️本篇博客开始要继续给大家介绍线程同步和互斥相关的知识。多线程是如何进行同步与互斥操作的,下面我来和大家一起聊一聊~目录🌏线程互斥🌲概念🌲互斥量mutex🌲互斥量的接口🌲互斥量的原理&... 查看详情

再探同步与互斥(代码片段)

文章目录线程锁种无锁编程乐观锁设计一个乐观锁悲观锁如何选择自旋锁互斥锁读写锁设计读写锁使用读写锁死锁pthread_mutex_timedlock死锁产生死锁的避免与解决的基本方法lock_guard&&unique_locklock_guardunique_lock条件变量条件变量... 查看详情

再探同步与互斥(代码片段)

文章目录线程锁种无锁编程乐观锁设计一个乐观锁悲观锁如何选择自旋锁互斥锁读写锁设计读写锁使用读写锁死锁pthread_mutex_timedlock死锁产生死锁的避免与解决的基本方法lock_guard&&unique_locklock_guardunique_lock条件变量条件变量... 查看详情

互斥锁和条件变量(代码片段)

...念通信中的同步指协同步调,按预定的先后次序运行线程同步,指一个线程发出某一个功能调用时。在没有得到结果之前,该调用不返回,同时其他线程为保证数据一致性,不能调用该数据。“同步”的目的&#... 查看详情