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…...
达摩院PALM春联模型多场景落地:政务大厅自助春联机解决方案
达摩院PALM春联模型多场景落地:政务大厅自助春联机解决方案 春节贴春联,是咱们中国人传承千年的文化习俗。一副好春联,不仅承载着对新年的美好祝愿,也体现着家庭的品味和格调。但你知道吗?现在写春联这件事࿰…...
OpenClaw故障自愈方案:Qwen3-32B镜像异常重启监控
OpenClaw故障自愈方案:Qwen3-32B镜像异常重启监控 1. 问题背景与解决思路 上周我的OpenClaw自动化助手突然"罢工"了——原本应该定时执行的日报生成任务没有按时完成。排查后发现是底层Qwen3-32B模型服务因OOM异常退出。这种情况在长期运行的AI服务中并…...
别再只盯着GPS了!从手机导航到无人机测绘,聊聊SPP、DGPS、RTK、PPP这几种定位技术到底该怎么选?
定位技术实战指南:从厘米级精度到全球覆盖的智能决策 站在一片待测绘的工地上,无人机工程师小王正面临一个关键抉择——该为这批新设备配置哪种定位模块?RTK的厘米级精度令人心动,但架设基准站的成本让他犹豫;PPP技术号…...
FastAPI流式AI接口设计陷阱大全(2024高频真题+源码级调试实录)
第一章:FastAPI流式AI接口设计陷阱大全(2024高频真题源码级调试实录)流式响应被中间件静默截断 FastAPI 默认启用的 Starlette 中间件(如 HTTPSRedirectMiddleware 或自定义日志中间件)可能在未显式处理 StreamingResp…...
互联网大厂 Java 面试实战:一次“高并发系统追问”下的真实对话
在大多数 Java 面试中,真正拉开差距的从来不是“你会多少知识点”,而是当系统出现问题时,你是否知道该怎么扛。很多候选人熟悉各种八股文,但一旦进入场景题就会卡住。下面通过一场更贴近真实大厂风格的面试,对话式还原…...
在单细胞测序数据分析中,barcodes、features和matrix是三个最核心的基础文件,它们共同构成了所有分析的基石。
在GEO(Gene Expression Omnibus)数据库中下载单细胞数据时,最常见的数据存储和提供形式主要有以下四种类型:10x Genomics 标准格式(最主流)在GEO的数据集中,我们通常会找到一个包含以下三个核心…...
MAC动态库加载路径优化:从@rpath到install_name_tool实战解析
1. 动态库加载路径问题的本质 当你第一次在Mac上遇到"Library not loaded"错误时,那种感觉就像在陌生城市迷了路。我清楚地记得自己早期开发时,控制台突然抛出红色错误信息的场景: dyld: Library not loaded: libAwesome.dylibRefe…...
手把手教你用NOAA气象数据做可视化分析(含常见字段解析与避坑指南)
手把手教你用NOAA气象数据做可视化分析(含常见字段解析与避坑指南) 气象数据可视化是理解气候模式、分析极端天气事件的重要工具。美国国家海洋和大气管理局(NOAA)提供的全球历史气候网络日数据(GHCN-Daily࿰…...
终极指南:如何用LanceDB向量数据库构建智能学习资源检索系统
终极指南:如何用LanceDB向量数据库构建智能学习资源检索系统 【免费下载链接】lancedb Developer-friendly, serverless vector database for AI applications. Easily add long-term memory to your LLM apps! 项目地址: https://gitcode.com/gh_mirrors/la/lanc…...
利用快马平台快速构建高清乱码生成器:编码错误可视化原型开发指南
最近在调试一个多语言网站时,遇到了各种编码问题导致的乱码现象。为了更直观地理解不同编码错误的表现形式,我尝试用InsCode(快马)平台快速搭建了一个高清乱码生成器,效果出乎意料地好。下面分享下这个项目的实现思路和具体操作: …...
