并发编程--条件量与死锁及其解决方案
并发编程–条件量与死锁及其解决方案
文章目录
- 并发编程--条件量与死锁及其解决方案
- 1.条件量
- 1.1条件量基本概念
- 1.2条件量的使用
- 2. 死锁
1.条件量
1.1条件量基本概念
在许多场合中,程序的执行通常需要满足一定的条件,条件不成熟的时候,任务应该进入睡眠阻塞等待,条件成熟时应该可以被快速唤醒。另外,在并发程序中,会其他任务同时访问该条件,因此任何时候都必须以互斥的方式对条件进行访问。条件量就是专门解决上述场景的逻辑机制。
注意,上述表述中,条件和条件量是两个不同的东西,所谓条件就是指程序要继续运行所需要的前提条件,比如文件是否读完、内存是否清空等具体的场景限定,而条件量(即pthread_cond_t)是本节课件要讨论的一种同步互斥变量,专用于解决上述逻辑场景。

条件量的逻辑
说明:
- 在进行条件判断前,先加锁(防止其他任务并发访问)
- 成功加锁后,判断条件是否允许
- 若条件允许,则直接操作临界资源,然后释放锁
- 若条件不允许,则进入条件量的等待队列中睡眠,并同时释放锁
- 在条件量中睡眠的任务,可以被其他任务唤醒,唤醒时重新判定条件是否允许程序继续执行,当然也是必须先加锁。
1.2条件量的使用
条件量一般要跟互斥锁(或二值信号量)配套使用,互斥锁提供锁住临界资源的功能,条件量提供阻塞睡眠和唤醒的功能。
一般流程示例
以取款为例,假设有多个任务可同时访问存款余额 balance,其中某个任务希望从中取出 ¥100 元,并且要求满足如下逻辑:
- 如果余额中有大于等于100元,则立即取出
- 如果余额小于100元,则进入睡眠等待
- 当有别的任务修改了余额时可被唤醒,并继续判定是否可取款
pthread_mutex_t m; // 互斥锁
pthread_cond_t v; // 条件量// 银行余额(全局变量,意味着有别的进程可随时访问)
extern int balance;int main()
{// 1,初始化pthread_mutex_init(&m, NULL);pthread_cond_init(&v, NULL);// 2,对m加锁pthread_mutex_lock(&m);// 2,当条件不允许时,进入条件量中睡眠// 进入睡眠时,会自动对m解锁// 退出睡眠时,会自动对m加锁while(balance < 100)pthread_cond_wait(&v, &m);// 3,取款balance -= 100;// 4,对m解锁pthread_mutex_unlock(&m);
}
其他任务,可以在适当的时候,通过如下接口来唤醒处于睡眠态的任务:
// 单个唤醒,唤醒第一个进入条件量中睡眠的任务
pthread_cond_signal(&v);// 集体唤醒,唤醒进入条件量中睡眠的所有任务
pthread_cond_broadcast(&v);
2. 死锁
死锁指的是由于某种逻辑问题,导致等待一把永远无法获得的锁的困境。比如最简单的是同一线程,连续对同一锁资源进行加锁,就进入了死锁。

最简单的死锁示例
pthread_mutex_t m;int main()
{pthread_mutex_init(&m, NULL);// 正常加锁pthread_mutex_lock(&m);// 未释放锁前重复加锁,进入死锁状态pthread_mutex_lock(&m);// 下面的代码永远无法执行......
}
以上死锁的例子,可以通过仔细检查代码得以避免,但在现实场景中,有些产生死锁的情况是无法避免的,比如如下情形:
一条线程持有一把锁,期间不能屏蔽取消指令
然后又恰巧被取消指令强制终止,此时死锁的产生变得不可避免。
产生死锁示例
void *routine(void *arg)
{thread_pool *pool = (thread_pool *)arg;struct task *p;while(1){// 操作临界资源之前,加锁pthread_mutex_lock(&pool->lock);// 条件不允许时,进入条件量等待while(pool->waiting_tasks == 0 && !pool->shutdown)pthread_cond_wait(&pool->cond, &pool->lock);// 条件允许时,操作临界资源p = pool->task_list->next;pool->task_list->next = p->next;pool->waiting_tasks--;// !!! 注意 !!!// 线程若恰好在此处被意外终止,将导致死锁// 解锁pthread_mutex_unlock(&pool->lock);// 其他操作pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);(p->do_task)(p->arg);pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);free(p);}pthread_exit(NULL);
}
上述代码中,若线程在中间被取消,则导致死锁。对于这种情况,一个可行的解决办法是:
- 提前准备一个解锁处理函数,并将其压入线程专用的函数栈中备用。
- 准备操作临界资源,加锁
- 操作临界资源
- 重点:
- 若线程在此期间意外终止,则会自动调用处理函数解锁
- 解锁
- 在函数栈中弹出处理函数。
说明:
上述做法实际上相当于现实生活中的立遗嘱,因为人去世之后是无法再做任何事情的,因此为了防止死亡在关键阶段意外到来,可以在提前立遗嘱,万一不幸遇到该情况就有了预案(处理函数),但如果并未发生此种情形,那么就将遗嘱作废(弹出处理函数且不执行)即可。
根据以上思路,可将上述代码改良为如下代码:
// 意外处理函数:
// 自动解锁
void handler(void *arg)
{pthread_mutex_unlock((pthread_mutex_t *)arg);
}void *routine(void *arg)
{thread_pool *pool = (thread_pool *)arg;struct task *p;while(1){/*** push a cleanup functon handler(), make sure that** the calling thread will release the mutex properly** even if it is cancelled during holding the mutex.**** NOTE:** pthread_cleanup_push() is a macro which includes a** loop in it, so if the specified field of codes that ** paired within pthread_cleanup_push() and pthread_** cleanup_pop() use 'break' may NOT break out of the** truely loop but break out of these two macros.** see line 56 below.*///================================================//pthread_cleanup_push(handler, (void *)&pool->lock); // 提前准备好意外处理函数pthread_mutex_lock(&pool->lock);//================================================//// 1, no task, and is NOT shutting down, then waitwhile(pool->waiting_tasks == 0 && !pool->shutdown)pthread_cond_wait(&pool->cond, &pool->lock);// 2, no task, and is shutting down, then exitif(pool->waiting_tasks == 0 && pool->shutdown == true){pthread_mutex_unlock(&pool->lock);pthread_exit(NULL); // CANNOT use 'break';}// 3, have some task, then consume itp = pool->task_list->next;pool->task_list->next = p->next;pool->waiting_tasks--;//================================================//pthread_mutex_unlock(&pool->lock); pthread_cleanup_pop(0); // 弹出处理函数且不执行//================================================//pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);(p->do_task)(p->arg);pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);free(p);}pthread_exit(NULL);
}
注意:
pthread_cleanup_push()用于将处理函数填入栈中,在线程意外终止后会被自动调用。pthread_cleanup_pop()用于将栈中的处理函数弹出,若参数为0则意味着不执行,参数不为零则意味着执行该函数。pthread_cleanup_push()和pthread_cleanup_pop()必须成对出现。
相关文章:
并发编程--条件量与死锁及其解决方案
并发编程–条件量与死锁及其解决方案 文章目录 并发编程--条件量与死锁及其解决方案1.条件量1.1条件量基本概念1.2条件量的使用 2. 死锁 1.条件量 1.1条件量基本概念 在许多场合中,程序的执行通常需要满足一定的条件,条件不成熟的时候,任务…...
JAVA SE 自我总结
目录 1. 字面常量 2. 数据类型 3. 变量 4. 类型转换 5. 实参和形参的关系 6. 数组 6.1 数组的概念 6.2 动态初始化 6.3 静态初始化 7. 数据区 编辑 8. 数组的拷贝 8.1 赋值拷贝 8.2 方法拷贝 9. 代码块 10. 内部类 10.1 实例内部类 10.2 静态内部类 10.3 …...
RAG创建向量数据库:docsearch = FAISS.from_texts(documents, embeddings)
RAG创建向量数据库:docsearch = FAISS.from_texts(documents, embeddings) 代码解释 docsearch = FAISS.from_texts(documents, embeddings) 这行代码主要作用是基于给定的文本集合创建一个向量数据库(这里使用 FAISS 作为向量数据库工具 )。具体说明如下: FAISS :FAISS …...
虚幻引擎5-Unreal Engine笔记之“将MyStudent变量设置为一个BP_Student的实例”这句话如何理解?
虚幻引擎5-Unreal Engine笔记之“将MyStudent变量设置为一个BP_Student的实例”这句话如何理解? code review! 文章目录 虚幻引擎5-Unreal Engine笔记之“将MyStudent变量设置为一个BP_Student的实例”这句话如何理解?理解这句话的关键点1.类(…...
鸢尾花分类的6种机器学习方法综合分析与实现
鸢尾花分类的6种机器学习方法综合分析与实现 首先我们来看一下对应的实验结果。 数据准备与环境配置 在开始机器学习项目前,首先需要准备编程环境和加载数据。以下代码导入必要的库并加载鸢尾花数据集: import numpy as np import pandas as pd impo…...
vite,Vue3,ts项目关于axios配置
一、安装依赖包 npm install axios -S npm install qs -S npm install js-cookie 文件目录 二、配置线上、本地环境 与src文件同级,分别创建本地环境文件 .env.development 和线上环境文件 .env.production # 本地环境 ENV = development # 本地环境接口地址 VITE_API_URL =…...
mysql:重置表自增字段序号
情况一:清空表数据后重置自增 ID 如果你希望清空表中的所有数据,并将自增 ID 重置为初始值(通常为 1) 1、truncate truncate table tb_dict; 2、delete 配合 alter 语句 delete from tb_dict; alter table tb_dict AUTO_INCR…...
STM32 模块化开发指南 · 第 4 篇 用状态机管理 BLE 应用逻辑:分层解耦的实践方式
本文是《STM32 模块化开发实战指南》第 4 篇,聚焦于 BLE 模块中的状态管理问题。我们将介绍如何通过有限状态机(Finite State Machine, FSM)架构,实现 BLE 广播、扫描、连接等行为的解耦与可控,并配合事件队列驱动完成主从共存、低功耗友好、状态清晰的 BLE 应用。 一、为…...
HTML — 浮动
浮动 HTML浮动(Float)是一种CSS布局技术,通过float: left或float: right使元素脱离常规文档流并向左/右对齐,常用于图文混排或横向排列内容。浮动元素会紧贴父容器或相邻浮动元素的边缘,但脱离文档流后可能导致父容器高…...
IP节点详解及国内IP节点获取指南
获取国内IP节点通常涉及网络技术或数据资源的使用,IP地址作为网络设备的唯一标识,对于网络连接和通信至关重要。详细介绍几种修改网络IP地址的常用方法,无论是对于家庭用户还是企业用户,希望能找到适合自己的解决方案。以下是方法…...
AD9253 LVDS 高速ADC驱动开发
1、查阅AD9253器件手册 2、查阅Xilinx xapp524手册 3、该款ADC工作在125Msps下,14bit - 2Lane - 1frame 模式。 对应:data clock时钟为500M DDR mode。data line rate:1Gbps。frame clock:1/4 data clock 具体内容:…...
pycharm2024.3.5版本配置conda踩坑
配置解释器是conda时,死活选不到自己的环境 看了很多,都是说要选scripts下的conda.exe 都没用 主要坑在于这是新版的pycharm 是配置condabin 下的 conda.bat 参考:PyCharm配置PyTorch环境(完美解决找不到Conda可执行文件python.exe问题) …...
【异常处理】Clion IDE中cmake时头文件找不到 头文件飘红
如图所示是我的clion项目目录 我自定义的data_structure.h和func_declaration.h在unit_test.c中无法检索到 cmakelists.txt配置文件如下所示: cmake_minimum_required(VERSION 3.30) project(noc C) #设置头文件的目录 include_directories(${CMAKE_SOURCE_DIR}/…...
14 - VDMA彩条显示实验
文章目录 1 实验任务2 系统框图3 硬件设计4 软件设计 1 实验任务 本实验任务是PS端写彩条数据至DDR3内存中,然后通过PL端的VDMA IP核将彩条数据通过HDMI接口输出显示。 2 系统框图 本实验是用HDMI接口固定输出1080P的彩条图,所以: rgb2lc…...
每天学一个 Linux 命令(13):touch
Linux 文件管理命令:touch touch 是 Linux 中一个简单但高频使用的命令,主要用于创建空文件或修改文件的时间戳(访问时间、修改时间)。它是文件管理和脚本操作的实用工具。 1. 命令作用 创建空文件:快速生成一个或多个空白文件。更新时间戳:修改文件的访问时间(Access …...
PromptUp 网站介绍:AI助力,轻松创作
1. 网站定位与核心功能 promptup.net 可能是一个面向 创作者、设计师、营销人员及艺术爱好者 的AI辅助创作平台,主打 零门槛、智能化的内容生成与优化。其核心功能可能包括: AI艺术创作:通过输入关键词、选择主题或拖放模板,快速生成风格多样的数字艺术作品(如插画、海报…...
高级java每日一道面试题-2025年3月26日-微服务篇[Nacos篇]-在Spring Cloud项目中如何集成Nacos?
如果有遗漏,评论区告诉我进行补充 面试官: 在Spring Cloud项目中如何集成Nacos? 我回答: 在Spring Cloud项目中集成Nacos,可以充分利用Nacos作为服务注册与发现中心以及配置管理中心的功能。以下是详细的步骤和说明,帮助你完成这一集成过程…...
AI 大语言模型 (LLM) 平台的整体概览与未来发展
📋 分析报告:AI 大语言模型 (LLM) 平台的整体概览与未来发展 自动生成的结构化分析报告 💻 整体概述:AI LLM 平台的市场现状与发展动力 随着人工智能技术的飞速发展,大语言模型(Large Language Models, L…...
Java中的Map vs Python字典:核心对比与使用指南
一、核心概念 1. 基本定义 Python字典(dict) :动态类型键值对集合,语法简洁,支持快速查找。Java Map:接口,常用实现类如 HashMap、LinkedHashMap,需声明键值类型(泛型&…...
人工智能100问☞第3问:深度学习的核心原理是什么?
目录 一、通俗解释 二、专业解析 三、权威参考 深度学习的核心原理是通过构建多层神经网络结构,逐层自动提取并组合数据特征,利用反向传播算法优化参数,从而实现对复杂数据的高层次抽象和精准预测。 一、通俗解释 深度学习的核心原理,就像是教计算机像婴儿…...
金能电力:配电房为什么离不开绝缘胶板
在当今电力系统日益复杂、对供电稳定性与安全性要求极高的时代,每一个细节都关乎着电力供应的顺畅以及工作人员的生命安全。而配电房里常常被大家忽视的绝缘垫,实则起着至关重要的 “守护” 作用。今天,金能电力就来给大家详细讲讲配电房为什…...
Python 深度学习实战 第1章 什么是深度学习代码示例
第1章:什么是深度学习 内容概要 第1章介绍了深度学习的背景、发展历史及其在人工智能(AI)和机器学习(ML)中的地位。本章探讨了深度学习的定义、其与其他机器学习方法的关系,以及深度学习在近年来取得的成…...
【模块化拆解与多视角信息1】基础信息:隐藏的筛选规则——那些简历上没说出口的暗号
写在最前 作为一个中古程序猿,我有很多自己想做的事情,比如埋头苦干手搓一个低代码数据库设计平台(目前只针对写java的朋友),比如很喜欢帮身边的朋友看看简历,讲讲面试技巧,毕竟工作这么多年,也做到过高管,有很多面人经历,意见还算有用,大家基本都能拿到想要的offe…...
【HD-RK3576-PI】VNC 远程桌面连接
在当今数字化时代,高效便捷的操作方式是技术爱好者与专业人士的共同追求。对于使用 HD-RK3576-PI微型单板计算机的用户而言,当面临没有显示屏的场景时,如何实现远程操作桌面系统呢?别担心,VNC 远程桌面连接将为你解决这…...
Vue.js 中 v-if 的使用及其原理
在 Vue.js 的开发过程中,条件渲染是一项极为常见的需求。v-if指令作为 Vue.js 实现条件渲染的关键手段,能够根据表达式的真假来决定是否渲染某一块 DOM 元素。它在优化页面展示逻辑、提升用户体验等方面发挥着重要作用。接下来,我们就深入探讨…...
电梯广告江湖的终局:分众 “吃掉” 新潮,是救赎还是迷途?
文 / 大力财经 作者 / 魏力 导言:商业世界的底层运行法则,从来都是能量流动的自然映射。宇宙第一性原理和运行法则是,能量大的吸引能量小的。电梯里的战争与和平,从对抗到合并,成为中国商业竞争史中关于博弈与进化的…...
第十六届蓝桥杯大赛软件赛省赛 C/C++ 大学B组
由于官方没有公布题目的数据, 所以代码仅供参考 1. 移动距离 题目链接:P12130 [蓝桥杯 2025 省 B] 移动距离 - 洛谷 【问题描述】 小明初始在二维平面的原点,他想前往坐标 (233, 666)。在移动过程中,他 只能采用以下两种移动方式…...
如何在 CentOS 7 系统上以容器方式部署 GitLab,使用 ZeroNews 通过互联网访问 GitLab 私有仓库,进行代码版本发布与更新
第 1 步: 部署 GitLab 容器 在开始部署 GitLab 容器之前,您需要创建本地目录来存储 GitLab 数据、配置和日志: #创建本地目录 mkdir -p /opt/docker/gitlab/data mkdir -p /opt/docker/gitlab/config mkdir -p /opt/docker/gitlab/log#gi…...
第1章 对大型语言模型的介绍
人类正处在一个关键转折点。自2012年起,基于深度神经网络的人工智能系统研发进入快速通道,将这一技术推向了新高度:至2019年底,首个能够撰写与人类文章真假难辨的软件系统问世,这个名为GPT-2(生成型预训练变…...
Quartus II的IP核调用及仿真测试
目录 第一章 什么是IP核?第二章 什么是LPM?第一节 设置LPM_COUNTER模块参数第二节 仿真 第三章 什么是PLL?第一节 设置ALTPLL(嵌入式锁相环)模块参数第二节 仿真 第四章 什么是RAM?第一节 RAM_1PORT的调用第…...
