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…...
处理k8s中创建ingress失败
创建ingress: 如果在创建过程中出错了: 处理方法就是: kubectl get ValidatingWebhookConfiguration kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission 然后再次创建,发现可以:...
Redis高可用集群架构
高可用集群架构 哨兵模式缺点 主从切换阶段, redis服务不可用,高可用不太友好只有单个主节点对外服务,不能支持高并发单节点如果设置内存过大,导致持久化文件很大,影响数据恢复,主从同步性能 高可用集群…...
JAVA常见问题解答:解决Java 11新特性兼容性问题的六个步骤
引言: 随着技术的不断发展,Java作为一种被广泛使用的编程语言,也在不断更新和改进。Java 11作为Java的最新版本,带来了许多新的特性和改进。然而,对于一些老旧的Java应用程序来说,升级到Java 11可能会带来一…...
【C语言】深入理解指针(1)
目录 前言 (一)内存与地址 从实际生活出发 地址 内存 内存与地址关系密切 (二)指针变量 指针变量与取地址操作符 指针变量与解引用操作符 指针的大小 指针的运算 指针 - 整数 指针-指针 指针的关系运算 指针的类型的…...
MySQL的系统信息函数
系统信息函数让你更好的使用MySQL数据库 1、version()函数 查看MySQL系统版本信息号 select version();2、connection_id()函数 查看当前登入用户的连接次数 直接调用CONNECTION_ID()函数--不需任何参数--就可以看到当下连接MySQL服务器的连接次数,不同时间段该…...
python中.format() 方法
.format() 方法是一种用于格式化字符串的方法,它允许将变量的值插入到字符串中的占位符位置上。该方法可以接受一个或多个参数,并根据给定的格式规则将它们插入到字符串中。 下面是一些使用 .format() 方法的示例: # 基本用法 name "…...
【新手解答8】深入探索 C 语言:递归与循环的应用
C语言的相关问题解答 写在最前面问题:探索递归与循环在C语言中的应用解析现有代码分析整合循环示例代码修改注意事项结论 延伸:递归和循环的退出条件设置解析使用递归使用循环选择适合的方法 写在最前面 一位粉丝私信交流,回想起了当初的我C…...
服务器中深度学习环境的配置
安装流程 11.17 日,周末去高校参加学术会议,起因, 由于使用了某高校内的公共有线网络, 远程连接服务器后,黑客利用 ssh 开放的 22 端口, 篡改了主机的配置, 使得只要一连上网络, 服…...
html实现各种好看的鼠标滑过图片特效模板
文章目录 1.鼠标悬浮效果1.1 渐动效果1.2 渐变效果1.3 边框效果1.4 线行效果1.5 图标效果1.6 块状效果1.7 边线效果1.8 放大效果1.9 渐出效果1.10 痕迹效果1.11 交叉效果1.12 着重效果1.13 详展效果1.14 能动效果1.15 明细效果 2.主要源码2.1 源代码 源码下载 作者:…...
leetcode:LCR 122. 路径加密python3解法)
难度:简单 假定一段路径记作字符串 path,其中以 "." 作为分隔符。现需将路径加密,加密方法为将 path 中的分隔符替换为空格 " ",请返回加密后的字符串。 示例 1: 输入:path "a.a…...
百度快照 直接进网站/google移动服务应用优化
监控系统进程资源的使用情况是IT运维的常规操作。在实际工作中,运维人员有可能遇到可以使用Zabbix Agent监控linux系统进程,却无法监控windows系统进程的情况。这是因为Zabbix Agent可以通过linux系统内置键值进行监控进程,而windows平台则不…...
织梦源码怎样做单页网站/谷歌seo外链
三台主机192.168.191.106(代号106) 产生日志192.168.191.107(代号107) 实现存放日志的数据库192.168.191.173(代号173) 实现日志报表1、实现rsyslog将日志记录于MySQL中(1)在107上:yum install mariadb-serversystemctl start mariadbmysql_secure_installati…...
网站模板后台怎么做/营销咨询服务
10.3去看了穗港澳动漫展,08年广州难得的一次动漫展......比较近距离地看了下川みくに,气氛很好。贴个相册地址,有兴趣的可以去看下哇。[url]http://photo.163.com/photos/squareram/160820460/[/url]转载于:https://blog.51cto.com/zeroyang/…...
用家里的路由器做网站/大数据营销案例
2019独角兽企业重金招聘Python工程师标准>>> TODO:小程序的用户身份 小程序的用户身份有三种,一个管理员,二十个开发者,四十个体验者 1.管理员,可设置风险操作保护、风险操作提醒等操作。 a)修改管理员&…...
2022年时事新闻摘抄/seo可以从哪些方面优化
所谓程序锁就是当用户启动某个程序的时候需要用户校验,如果校验成功,则进入应用程序. 也可以用于功能锁,也就是当用户使用程序的某个时,进行进行校验如果校验成功则进入该功能. 效果如下图所示: 该项目是google的开源项目. 下载地址:http://download.csdn.net/detail/johnny…...
分销平台网站建设桂林/微信广告投放平台
1、安装cron工具:apt-getinstall cron 2、开启定时任务:crontab –e 定时任务语句格式为:执行周期命令。 周期有5个域,分别是分,时,日(day of month),月(month of year)&…...