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内部执行的操作如下:
- 解锁互斥量mutex
- 阻塞调用线程,直至另一个线程就条件变量cond发出信号
- 重新锁定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指定该条件变量可用于进程内的线程间同步,还是用于进程间同步。 但是,在linux的glibc实现中,进程间同步却存在着一个缺陷。将导致问题扩散,非常严重。原因如下: 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_timedwait,pthread_cond_signal/pthread_cond_broadcast都必须先获取锁,然后操作数据,完毕释放锁。 在多进程上,如果某个进程在pthread_cond_xxx的接口里获取了锁以后,因某种原因退出了(比如某个线程运行异常了)。那么,悲剧来了,锁没有释放。于是,其他的进程只要调用到这个条件变量的接口,将因为获取不到锁而等待,且一直等待下去。一个进程的异常导致所有进程的异常。 令人困惑的是,mutex也可用于进程间互斥,pthread_mutex_setpshared设置。但是pthread_mutex_t却支持这种场景,进程获取到了mutex后复位了,没有释放锁,OS帮忙释放(需要在mutex初始化时设置pthread_mutexattr_setrobust_np)。 同样可用于进程间的条件变量为什么没有这个机制? |
相关文章:
linux 应用层同步与互斥机制之条件变量
2、条件变量 互斥量防止多个线程同时访问同一共享变量。(我们称为互斥) 有一种情况,多个线程协同工作。一个线程的消费需要等待另一个线程的产出。必须线程B完成了应有的任务,满足了某一个条件,线程A才能继续执行。&…...

3.5毫米音频连接器接线方式
3.5毫米音频连接器接线方式 耳机插头麦克风插头 绘制电路图注意事项 3.5毫米音频连接器分为单声道开关型和无开关型如下图: sleeve(套筒) tip(尖端) ring(环) 耳机插头 麦克风插头 绘制电路图…...

智慧农田可视化大数据综合管理平台方案,EasyCVR助力农业高质量发展
一、背景需求 我国是农业大国,农业耕地面积达到20亿亩。随着物联网、大数据、人工智能等新一代信息技术与农业农村加速融合,以及国家对农业的重视,智慧农业对于我国农业现代化建设和实施乡村振兴战略具有重大引领与推动作用。在传统农田生产…...

python超详细基础文件操作【建议收藏】
文章目录 前言1 文件操作1.1 文件打开与关闭1.1.1 打开文件1.1.2 关闭文件 1.2 访问模式及说明 2 文件读写2.1 写数据(write)2.2 读数据(read)2.3 读数据(readlines)2.3 读数据(readline&#x…...

华为变革进展指数TPM的五个级别:试点级、推行级、功能级、集成级和世界级
华为变革进展指数TPM的五个级别:试点级、推行级、功能级、集成级和世界级 TPM(Transformation Progress Metrics,变革进展指标)用来衡量管理体系在华为的推行程度和推行效果,并找出推行方面的不足与问题,…...

vue el-select多选封装及使用
使用了Element UI库中的el-select和el-option组件来构建多选下拉框。同时,也包含了一个el-input组件用于过滤搜索选择项,以及el-checkbox-group和el-checkbox组件用于显示多选项。 创建组件index.vue (src/common-ui/selectMultiple/index.vue) <tem…...

大模型上下文学习(ICL)训练和推理两个阶段31篇论文
大模型都火了这么久了,想必大家对LLM的上下文学习(In-Context Learning)能力都不陌生吧? 以防有的同学不太了解,今天我就来简单讲讲。 上下文学习(ICL)是一种依赖于大型语言模型的学习任务方式…...
WordPress(安装比子主题文件)zibll-7.5.1
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、新建网站二、配置ssl三.配置伪静态四.上传文件五.添加本地访问前言 提示:这里可以添加本文要记录的大概内容: 首先,我们要先理解什么是授权原理。 原理就是我们大家运营网站,点击授权…...

蓝桥杯 动态规划
01 数字三角形 #include<bits/stdc.h> using namespace std; const int N105; using lllong long; ll a[N][N],dp[N][N]; int main(){int n;cin>>n;for(int i1;i<n;i){for(int j1;j<i;j){cin>>a[i][j];}}for(int i5;i>1;i--){for(int j1;j<i;j){…...

【图论】重庆大学图论与应用课程期末复习资料2-各章考点(计算部分)(私人复习资料)
图论各章考点 二、树1、避圈法(克鲁斯克尔算法)2、破圈法3、Prim算法 四、路径算法1、Dijkstra算法2、Floyd算法 五、匹配1、匈牙利算法(最大权理想匹配(最小权权值取反)) 六、行遍性问题1、Fleury算法&…...

整数和浮点数在内存中的存储(大小端详解)
目录 一、整数在内存中的存储 二、大小端字节序和字节序判断 2.1为什么有大小端? 2.2请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)-百度笔试题 方法一(char*强制类型转换)…...

SpringBoot 集成 ChatGPT,实战附源码
1 前言 在本文中,我们将探索在 Spring Boot 应用程序中调用 OpenAI ChatGPT API 的过程。我们的目标是开发一个 Spring Boot 应用程序,能够利用 OpenAI ChatGPT API 生成对给定提示的响应。 您可能熟悉 ChatGPT 中的术语“提示”。在 ChatGPT 或类似语…...

数据结构——希尔排序(详解)
呀哈喽,我是结衣 不知不觉,我们的数据结构之路已经来到了,排序这个新的领域,虽然你会说我们还学过冒泡排序。但是冒泡排序的性能不高,今天我们要学习的希尔排序可就比冒泡快的多了。 希尔排序 希尔排序的前身是插入排…...

C++ day53 最长公共子序列 不相交的线 最大子序和
题目1:1143 最长公共子序列 题目链接:最长公共子序列 对题目的理解 返回两个字符串的最长公共子序列的长度,如果不存在公共子序列,返回0,注意返回的是最长公共子序列,与前一天的最后一道题不同的是子序…...
ubuntu中删除镜像和容器、ubuntu20.04配置静态ip
1 删除镜像 # 短id sudo docker rmi 镜像id # 完整id sudo docker rmi 镜像id# 镜像名【REPOSITORY:TAG】 sudo docker rmi redis:latest2 删除容器 # 删除某个具体容器 sudo docker rm 容器id# 删除Exited状态/未运行的容器,三种命令均可 sudo docker rm docker …...

华为手环 8 五款免费表盘已上线,请注意查收
华为手环 8,作为一款集时尚与实用于一体的智能手环,不仅具备强大的功能,还经常更新的表盘样式,让用户掌控时间与健康的同时,也能展现自己的时尚品味。这不,12 月官方免费表盘又上新了,推出了五款…...

JOSEF约瑟 同步检查继电器DT-13/200 100V柜内安装,板前接线
系列型号 DT-13/200同步检查继电器; DT-13/160同步检查继电器; DT-13/130同步检查继电器; DT-13/120同步检查继电器; DT-13/90同步检查继电器; DT-13/254同步检查继电器; 同步检查继电器DT-13/200 100V柜内板前接线 一、用途 DT-13型同步检查继电器用于两端供电线路的…...

龙迅#LT8311X3 USB中继器应用描述!
1. 概述 LT8311X3是一款USB 2.0高速信号中继器,用于补偿ISI引起的高速信号衰减。通过外部下拉电阻器选择的编程补偿增益有助于提高 USB 2.0 高速信号质量并通过 CTS 测试。 2. 特点 • 兼容 USB 2.0、OTG 2.0 和 BC 1.2• 支持 HS、FS、LS 信令 • 自动检测和补偿 U…...
eclipse jee中 如何建立动态网页及服务的设置问题
第一次打开eclipse 时,设置工作区时,一定是空目录 进入后 File-----NEW------Dynamic Web Project 填 项目名,不要有大写 m1 next next Generate前面打对勾 finish 第一大步: window----Preferences type filter text 处填 :Serve…...

一张网页截图,AI帮你写前端代码,前端窃喜,终于不用干体力活了
简介 众所周知,作为一个前端开发来说,尤其是比较偏营销和页面频繁改版的项目,大部分的时间都在”套模板“,根本没有精力学习前端技术,那么这个项目可谓是让前端的小伙伴们看到了一丝丝的曙光。将屏幕截图转换为代码&a…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...