linux 应用层同步与互斥机制之条件变量

2、条件变量

互斥量防止多个线程同时访问同一共享变量。(我们称为互斥)

有一种情况,多个线程协同工作。一个线程的消费需要等待另一个线程的产出。必须线程B完成了应有的任务,满足了某一个条件,线程A才能继续执行。(我们称为同步)

条件变量就是来解决同步问题的。

2.1 条件变量产生背景

用一个典型的例子(生产-消费)说明:

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static int avail = 0;

/*生产者线程示意代码*/

s = pthread_mutex_lock(&mtx);

if(s != 0)

    do_err();

avail++;

s = pthread_mutex_unlock(&mtx);

if(s != 0)

    do_err();

/*消费者线程示意代码*/

for(;;){

    s = pthread_mutex_lock(&mtx);

    if(s != 0)

        do_err();

   

    while(avail > 0)

        avail--;

        /*do something*/

    }

   

    s = pthread_mutex_unlock(&mtx);

    if(s != 0)

        do_err();

   

}

   

上述代码,生产者线程在满足一定条件下,将avail++。消费者线程不停的循环检查变量avail的状态,一旦有可用资源,就进行消费处理。虽然可行,但循环检查会造成CPU的资源的浪费。条件变量就是为解决这一问题而设计:允许一个线程休眠(等待)直至接获另一线程的通知(收到信号)去执行某些操作。

2.2 条件变量初始化和销毁

条件变量的数据类型是pthread_cond_t。

静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER

动态初始化:pthread_cond_init

#include <pthread.h>

int

pthread_cond_init(pthread_mutex_t *restrict cond, const pthread_condattr_t *restrict attr);

                                                                                                                                            成功:0 失败:非0

涉及动态初始化的变量,就要有销毁

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *restrict mutex);                                                                                                                                          成功:0 失败:非0

条件变量销毁:pthread_cond_destroy

条件变量的初始化和销毁的注意事项,类似于互斥量。

2.3 条件变量的通知和等待

2.3.1 函数定义和基本用法

条件变量的主要操作是发送信号和等待。发送信号操作即通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变。等待操作是指在接收到一个通知前一直处于阻塞状态。

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

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

                                                                                                                                            成功:0 失败:非0

看一下手册上的解释:

The pthread_cond_wait() functions shall block on a condition variable。

The pthread_cond_signal() function shall unblock at least one of the threads that are blocked on the specified condition variable cond (if any threads are blocked on cond).

The pthread_cond_broadcast() function shall unblock all threads currently blocked on the specified condition variable cond.

在解释具体参数前,我们先利用这些新函数,优化一下上面的“生产-消费”例子,看一下基本用法。

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

static int avail = 0;

/*生产者线程示意代码*/

s = pthread_mutex_lock(&mtx);

if(s != 0)

    do_err();

avail++;

s = pthread_cond_signal(&cond);

if(s != 0)

    do_err();

s = pthread_mutex_unlock(&mtx);

if(s != 0)

    do_err();

/*消费者线程示意代码*/

for(;;){

    s = pthread_mutex_lock(&mtx);

    if(s != 0)

        do_err();

   

    while(avail == 0){   //注意,这里不能用if,用while

        s = pthread_cond_wait(&cond, &mtx);

        if(s != 0)

            do_err();

    }

    while(avail > 0)

        avail--;

        /*do something*/

    }

   

    s = pthread_mutex_unlock(&mtx);

    if(s != 0)

        do_err();

   

}

   

2.3.2 pthread_cond_wait函数用法

条件变量与互斥量的天然联系

pthread_cond_wait内部执行的操作如下:

  1. 解锁互斥量mutex
  2. 阻塞调用线程,直至另一个线程就条件变量cond发出信号
  3. 重新锁定mutex

所以,条件变量总是要与一个互斥量相关。大家自然也就明白了pthread_cond_wait的第二个参数的意义。pthread_cond_wait必须在pthread_mutex_lock和pthread_mutex_unlock之间。等待相同条件变量的所有线程在调用pthread_cond_wait时必须指定同一互斥量。

pthread_cond_wait中,解锁互斥量和陷入对条件变量的等待属于一个原子操作。调用该函数时,在调用线程陷入对条件变量的等待之前,其他线程不可能获取到该互斥量,也不可能就该条件变量发出信号。

pthread_cond_wait使用的通用原则

从上面“生产-消费”的例子中,可以看到ptread_cond_wait函数调用放在了一个while循环中,而不是用if来判断,这是使用条件变量等待条件触发时的一个通用的设计原则。当代码从pthread_cond_wait()返回时,并不能确定判断条件的状态,应该立即重新检查判断条件,在条件不满足的情况下继续休眠等待。

最典型情况时存在多个消费线程等待条件变量通知。如果生产线程调用pthread_cond_broadcast()来唤醒多个等待的消费线程,那么只能有一个消费线程能够获取资源,进入下一步处理,其他消费线程没有竞争到可用资源,只能继续wait。

思考一下:

如果生产线程调用pthread_cond_signal()来唤醒一个等待的消费线程,上面的情况还会出现吗?

在多核处理器下,pthread_cond_signal可能会激活多于一个线程(阻塞在条件变量上的线程)。结果就是,当一个线程调用pthread_cond_signal()后,多个调用pthread_cond_wait()pthread_cond_timedwait()的线程返回。这种效应就称为“虚假唤醒”。

所以pthread_cond_signal()手册中的说明是至少唤醒一个等待线程

不论是使用while还是if,都是引入了一个共享变量,来标识是否有可用资源。这里扩展一下,说明两个概念:边沿触发和水平触发。比如消费者代码如下写法:

/*消费者线程示意代码*/

for(;;){

    s = pthread_mutex_lock(&mtx);

    if(s != 0)

        do_err();

   

    s = pthread_cond_wait(&cond, &mtx);

    if(s != 0)

        do_err();

   

    while(avail > 0)

        avail--;

        /*do something*/

    }

   

    s = pthread_mutex_unlock(&mtx);

    if(s != 0)

        do_err();

   

}

调用pthread_cond_wait时,不加任何条件判断,直接就等着。会发生什么?

因为时多线程,生产者线程可能先运行,即:有可能在调用pthread_cond_wait前,生产者线程已经调用了pthread_cond_signal()。pthread_cond_signal就是发个信号,唤醒一个在等待的线程。如果没有在等待的,就这样了。这种不保留通知事件的情况,就是边沿触发。要求关心事件的线程必须提前做好准备。所以上面的写法,就有可能丢失事件。

当我们加入一个共享变量,作为判断条件时,这个变量实际起到了记录事件的作用,将事件的有效期延长了。这就是水平触发。编程水平触发后,消费者进入wait前,先判断是否有事件发生,这样就不会丢失事件。

2.3.3 pthread_cond_signal函数用法

这个函数的使用比较简单,调用pthread_cond_signal函数时,不一定非得使用mutex互斥量。

思考一个问题:当使用mutex互斥量时,调用pthread_cond_signal()函数发送信号的时机。是放在pthread_mutex_unlock之前还是之后?

之前:

pthread_mutex_lock

    xxxxxxx

pthread_cond_signal

pthread_mutex_unlock

缺点:在某些系统的实现中,会造成等待线程从内核中唤醒(由于cond_signal)然后又回到内核空间(因为cond_wait返回后会有原子加锁的行为)。如:线程A调用signal唤醒线程B后,还没有来得及调用unlock,就切换了。后来线程B先运行了,线程B被唤醒,准备进行lock操作,发现mutex还被占用,进入阻塞。这中间可能涉及内核层和用户层切换问题。所以一来一回会有性能损耗。

但是在LinuxThreads里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。

所以在Linux中推荐使用这种模式。

之后:

pthread_mutex_lock

    xxxxxxx

pthread_mutex_unlock

pthread_cond_signal

优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了

缺点:有可能在unlock之后,signal之前就被调度了。如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程),因为wait的那个线程在等cond,没有在等mutex。而这在上面的放中间的模式下是不会出现的。

所以,在Linux下最好pthread_cond_signal放中间,但从编程规则上说,两种都可以。

2.4 条件变量适用场景

类似于Mutex,条件变量也是可以用于同一进程的线程之间,也可以用于跨进程。

但不建议使用跨进程,因为比较复杂,除了设置条件变量的跨进程属性外,mutex也要跨进程。

有一篇资料提出,慎用进程间条件变量:

条件变量是用于多线程/多进程间同步,是一种典型的睡眠唤醒用法。P1等待某个事件的发生,P2触发事件,唤醒P1

条件变量在初始化时,可以通过接口pthread_condattr_setpshared指定该条件变量可用于进程内的线程间同步,还是用于进程间同步。

但是,在linuxglibc实现中,进程间同步却存在着一个缺陷。将导致问题扩散,非常严重。原因如下:

pthread_cond_t结构体是一个复杂的数据结构,包含了等待信息,多个进程都可能同时调用等待函数pthread_cond_wait/pthread_cond_timedwait,唤醒函数pthread_cond_signal/pthread_cond_broadcast,为了防止一个或者多个等待者、唤醒者同时操作pthread_cond_t的成员,必须进行互斥,所以,pthread_cond_t里还有一个锁。接口实现上,pthread_cond_wait/pthread_cond_timedwaitpthread_cond_signal/pthread_cond_broadcast都必须先获取锁,然后操作数据,完毕释放锁。

在多进程上,如果某个进程在pthread_cond_xxx的接口里获取了锁以后,因某种原因退出了(比如某个线程运行异常了)。那么,悲剧来了,锁没有释放。于是,其他的进程只要调用到这个条件变量的接口,将因为获取不到锁而等待,且一直等待下去。一个进程的异常导致所有进程的异常。

令人困惑的是,mutex也可用于进程间互斥,pthread_mutex_setpshared设置。但是pthread_mutex_t却支持这种场景,进程获取到了mutex后复位了,没有释放锁,OS帮忙释放(需要在mutex初始化时设置pthread_mutexattr_setrobust_np)。

同样可用于进程间的条件变量为什么没有这个机制?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/198173.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

帮企多城市分站系统源码+关键词排名优化推广 附带完整的搭建教程

随着市场竞争的加剧&#xff0c;企业对于网络营销的需求越来越多元化。传统的单一网站已经无法满足企业在网络营销方面的需求&#xff0c;因此我们需要开发一套多城市分站系统&#xff0c;以满足企业在不同地区、不同行业的需求。同时&#xff0c;我们还结合了关键词排名优化推…

外包干了2个月,技术明显退步了...

先说一下自己的情况&#xff0c;大专生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近5年的功能测试&#xff0c;今年11月份&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

UE4 双屏分辨率设置

背景&#xff1a; 做了一个UI 应用&#xff0c;需要在双屏上进行显示。 分辨率如下&#xff1a;3840*1080&#xff1b; 各种折腾&#xff0c;其实很简单&#xff1a; 主要是在全屏模式的时候 一开始没有选对&#xff0c;双屏总是不稳定。 全屏模式改成&#xff1a;Windows 之…

JS加密/解密之HOOK实战

之前的章节有介绍过Javascript的Hook相关的基础知识&#xff0c;相信大部分人也知道了什么是Hook&#xff0c;今天我们来讲一下Hook实战&#xff0c;实际的运用。 0x1.事上练 // 程序员们基本都喜欢简单精辟 直入主题 不喜欢咬文嚼字 我们先直接上代码 var _log console.log…

WEPY框架的小程序的坑

在WEPY框架开发小程序时&#xff0c;可能会遇到一些常见的坑点&#xff0c;以下是一些需要注意的地方&#xff1a; 组件使用不当&#xff1a;WEPY框架的组件使用方式和原生小程序有所不同&#xff0c;如果使用不当可能会导致性能 问题或者逻辑错误。因此&#xff0c;需要仔细阅…

深度学习推理(Inference)

深度学习推理&#xff08;Inference&#xff09;是指已经训练好的深度学习模型在新的、未见过的数据上进行预测或分类的过程。在训练阶段&#xff0c;模型通过学习输入数据的模式和特征来调整参数&#xff0c;而在推理阶段&#xff0c;模型将这些学到的知识应用于新的输入数据&…

23、什么是卷积的 Feature Map?

这一节介绍一个概念&#xff0c;什么是卷积的 Feature Map&#xff1f; Feature Map, 中文称为特征图&#xff0c;卷积的 Feature Map 指的是在卷积神经网络&#xff08;CNN&#xff09;中&#xff0c;通过卷积这一操作从输入图像中提取的特征图。 上一节用示意动图介绍了卷积算…

web自动化 -- pyppeteer

由于Selenium流行已久&#xff0c;现在稍微有点反爬的网站都会对selenium和webdriver进行识别&#xff0c;网站只需要在前端js添加一下判断脚本&#xff0c;很容易就可以判断出是真人访问还是webdriver。虽然也可以通过中间代理的方式进行js注入屏蔽webdriver检测&#xff0c;但…

MySQL笔记-第04章_运算符

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第04章_运算符1. 算术运算符2. 比较运算符3. 逻辑运算符4. 位运算符5. 运算符的优先级拓展&#xff1a;使用正则表达式查询 第04章_运算符 …

​ 海外服务器创新高地:亚马逊云科技树立云计算韧性标杆

云计算的大潮中&#xff0c;众多企业对云服务器的需求与日俱增。但随之而来的就是云服务器的运行对于企业的业务的重要性也越来越高。想象一下&#xff0c;如果在全球范围内运行的服务和应用程序遭遇意外中断&#xff0c;从而产生的重大影响可能会给一些企业带来严重损失。因此…

如何将整个文件内容加载到富文本控件?

众所周知&#xff0c;富文本控件&#xff0c;Rich Text Control&#xff0c;用来呈现文本内容的一个控件&#xff0c;功能上相对记事本来说更加丰富&#xff0c;但又不及 Word。 但&#xff0c;我们的目标又不是开发另外一个 Word。 我们可以使用 EM_STREAMIN 消息将整个文件…

ubuntu安装tomcat并配置前端项目

1.1查找 # 先更新 sudo apt update # 查找 apt search jdk1.2安装 sudo apt install openjdk-8-jdk1.3验证 java -version 2.安装tomcat 下载链接&#xff1a;Apache Tomcat - Apache Tomcat 8 Software Downloadshttps://tomcat.apache.org/download-80.cgi下载这个&…

LeetCode [中等]最大子数组和-动态规划

53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 贪心算法&#xff1a;若当前指针所指元素之前的和小于0&#xff0c;则丢弃当前元素之前的数列 动态规划&#xff1a;若下一个元素大于0&#xff0c;则将其加到当前元素上 思路&#xff1a; n 为数组长度&#xff…

vue+electron问题汇总

1. Vue_Bug Failed to fetch extension, trying 4 more times 描述&#xff1a;项目启动时报错 解决&#xff1a;注释图片中内容 2. Module not found: Error: Can’t resolve ‘fs’ in 描述&#xff1a;项目启动报错 解决&#xff1a;vue.config.js中添加图中数据 3.导入…

PTA 7-231 买文具

某小学要购置文具。批发市场中 A 牌的铅笔卖 5 元一支&#xff0c;C 牌的铅笔卖 2 元一支&#xff0c;D 牌的简易铅笔卖1元2只&#xff08;捆绑销售&#xff0c;只能买偶数只&#xff09;。 如果想用n元买n支笔&#xff0c;问有多少种买法&#xff1f;&#xff08;题目保证 n …

【单片机】单片机裸机实现多任务调度

RTOS vs 裸机多任务调度 实时操作系统RTOS的优点不必多说了&#xff0c;但是对于一些简单的业务需求&#xff0c;移植一个操作系统显得非常麻烦&#xff0c;并且占用系统资源&#xff0c;此时就可以考虑利用SysTick裸机实现多任务调度。 单片机裸机实现多任务调度的优点有 1、…

Vue2虚拟列表,umy-ui封装

一、起因 1、需求&#xff1a; 由于业务需求在页面一次性展示较多数据&#xff0c;不低于上千&#xff0c;但是每条数据涉及样式较多&#xff0c;数据渲染过多就会导致页面卡顿 2、满足&#xff1a; 大量数据加载&#xff1b;表格功能&#xff1a;列显隐、列顺序调整、固定、筛…

基于Java SSM框架实现汽车在线销售系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现汽车在线销售系统演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&a…

3.2.1.0 发布!时间转换函数+BI 集成+视图正式上线!

自 3.0 版本发布以来&#xff0c;经过研发人员和社区用户的不断努力&#xff0c;TDengine 进行了大量更新&#xff0c;产品稳定性和易用性也在不断提升。近日&#xff0c;TDengine 3.2.1.0 成功发布&#xff0c;该版本带来了一些重大功能优化&#xff0c;这些优化将进一步提升 …

spark sql基于CBO的优化

前言 spark sql基于CBO的优化是建立在物理计划层面的&#xff0c;原理是计算出所有可能的物理执行计划&#xff0c;并挑选成代价最小的物理执行计划。对于执行计划可以去看我的另一篇博客RBO优化 CBO的话主要用来调整inner join所涉及表的顺序 使用CBO准备 搜集所需表和列的…