Linux系统篇48——线程(十三) 线程安全、重入与死锁,可重入为什么是线程安全的子集
本文收录于「流浪」的系列专栏Linux系统⚙️C数据结构与算法PythonLangChain LangGraph️MySQL 数据库Git 工具计算机网络LLM大厂面试、八股学习筑基专栏 博客主页流浪 原创首发于 CSDN前言线程十二用两个信号量把判空判满提前到访问之前环形队列单生产单消费一把锁都不用。本篇收两笔账一笔算给函数——线程安全与可重入到底什么关系一笔算给锁——死锁怎么成型、怎么破。最后把 STL 和智能指针的线程安全边界标清楚。一、线程安全与重入1.1 两个词各描述谁1. 线程安全是执行流自身的健康状态线程安全说的是线程自身的健康状态多个执行流并发跑跑完结果照样正确、互相不破坏判定标准线程八引过 man 手册口径能被多个线程同时调用且结果照样正确的函数才叫线程安全反面就是全局资源没加保护——判断和减减的缝隙被别的线程钻了ticket 抢票是原型2. 重入描述的是函数的特征重入说的是函数的特征同一个函数前一次调用还没返回又一次进入了再次进入的那次执行能不能照样正确决定了这个函数可重入还是不可重入两个词的宾语不同——线程安全的宾语是执行流跑起来的状态重入的宾语是函数这件东西的属性线程安全与重入不在同一个层面前者说执行流安不安全后者说函数能不能被重复进入。分清各描述谁两个词就搅不到一起。1.2 重入的两种情况1. 多线程重入函数第一种情况多个线程并发调用同一个函数第一个没跑完第二个又进来了两个执行流各拿一层调用函数里的共享数据同时被两份执行踩——线程八的抢票现场就是这一种2. 信号导致一个执行流重复进入函数第二种情况一个线程正跑在函数里信号打进来处理函数里又调了同一个函数同一条执行流把函数夹了两层——内层跑完回外层外层接着跑两层同时活着线程八提过 glibc 把两个维度分开标MT-Safe 管多线程并发进入AS-Safe 管中断后重入两条路不同对函数的要求是同一个——重入进来的时候自己带的东西够不够用碰不碰外面的共享状态。1.3 可重入是线程安全的子集1. 可重入函数一定是线程安全的只碰自己局部变量和参数的函数每一层调用都有独立的一份栈帧变量各是各的多线程进、信号打断后进每一层拿到的都是自己那一份结果互不污染连重复进入都扛得住的函数多线程同时调用没有出错的路径2. 线程安全不一定可重入加锁能把不可重入函数改造成线程安全一次只放一个进来并发的数据竞争被锁挡住——线程八的结论但重入撞的是另一堵墙第一次进入持着锁跑到一半第二次进入在 lock 上等递归死锁等的是第一次把锁解开而第一次正等着第二次返回——自己等自己谁都返回不了3. 不考虑信号重入时两者可以不区分两个方向合起来是子集关系可重入函数一定是线程安全的线程安全的函数不一定是可重入的若不考虑信号导致的那一种重入两个词在安全角度可以不区分——线程安全侧重并发访问公共资源的安全可重入描述函数能否被重复进入把信号场景算进来两者就必须分开答加锁是线程安全的恰恰在重入场景卡死可重入 ⊆ 线程安全。线程安全用锁就买得到可重入得看函数碰了谁——只碰自己栈帧的可以碰了共享状态的加锁也救不了重入。1.4 不可重入的典型函数1. 四类典型函数特征为什么不可重入不保护共享变量全局、静态数据被多层执行同时改写ticket 抢票是原型返回静态变量指针static 缓冲只有一份第二次进入直接覆盖第一次的结果调用 malloc/free堆由全局链表管理改链表改到一半被重入链就断了调用标准 I/O 库全局缓冲区同理——手册口径stdio 全部函数不是异步信号安全的四类的共性只有一条——函数依赖了自己栈帧之外的共享状态。名单可以背机制记住这一条就推得出来。二、死锁与四个必要条件2.1 死锁是什么1. 死锁的定义一组执行流各自占着一部分资源不放手又互相申请对方手里的资源你等我的、我等你的谁也拿不到想要的、谁也不撒手里的永久等待——这个僵局就是死锁本文语境里资源主要是锁但死锁不挑资源两把锁、锁加条件变量、锁加信号量都能成型2. 出现过的两个现场线程十推演过拿着锁去等条件变量、锁又攥在手里——唤醒的人进不了临界区等的人和持锁的人互卡线程十二的面试题里算过生产者持锁睡在信号量上消费者进不去、空位腾不出——双双卡死两个现场角色不同僵局的共同点一样占着的资源不放手等着的资源在别人手里2.2 双锁交叉申请的时序1. 僵局成型的四步线程一锁住锁 A线程二锁住锁 B——各持一把互不相干线程一申请锁 B锁被线程二持有睡下等待锁 A 不放手线程二申请锁 A锁被线程一持有睡下等待锁 B 不放手双方各持一把、各等一把能解开对方的那个动作永远轮不到执行2.3 死锁的四个必要条件1. 互斥资源一次只能被一个执行流占用——这是锁的本职线程九拆互斥锁原理拆的就是它破坏点资源能同时使用就无需排队但加锁就是为了独占资源所以这个死锁条件最难打破。2. 请求与保持一个执行流因请求资源而阻塞时对已获得的资源保持不放破坏点要新的就别攥旧的——申请前先释放已有的或者一次全拿3. 不剥夺一个执行流已获得的资源在未使用完之前不能强行剥夺破坏点等不到就上交——把已持有的资源还回去稍后重来4. 循环等待若干执行流之间形成一种头尾相接的循环等待资源关系破坏点给资源排个全序所有执行流按同一个顺序申请等待关系排成一条直线接不成环5. 四条同时成立死锁才成立四个条件都是必要条件死锁发生四条必然全部成立反过来任意一条被破坏死锁就锁不成——「必要条件」的准确含义就是缺一不可这也是避免死锁的全部抓手防死锁不用四条全防盯住最好破的那一条下手就够——互斥动不得就从剩下三条里挑。2.4 如何避免死锁1. 破坏必要条件的四个抓手资源一次性分配——破坏请求与保持要拿一次全拿不出现占着 A 等 B 的姿势申请带超时——破坏不剥夺等不到就超时返回把手里已持有的还回去稍后重试加锁顺序一致——破坏循环等待所有线程按同一顺序拿锁等待关系成一条直线头尾接不成环避免锁未释放的场景占着资源的窗口越短僵局越难凑成——RAII 封装的 LockGuard 在任何路径都还锁就是这一招的落地2. 银行家算法死锁避免的经典算法把资源借出去之前先推演一圈——借给这批执行流之后剩余资源还能不能让所有人按某个顺序全部跑完能全部跑完的顺序叫安全序列找得到就借、找不到就先不借——宁可不借不进僵局3. std::lock 一次性锁多把C 层给了现成工具std::lock 用死锁避免策略一次锁两把不存在锁住一把、卡在等另一把的窗口配 std::unique_lock 的 defer_lock构造时只包住锁不加锁最后一起上std::mutex mtx1,mtx2;// defer_lock构造时只绑定锁、不立刻加锁std::unique_lockstd::mutexlk1(mtx1,std::defer_lock);std::unique_lockstd::mutexlk2(mtx2,std::defer_lock);// 一次性锁两把顺序死锁从根上避开std::lock(lk1,lk2);破坏条件是预防银行家是避免std::lock 是工具——三层防的是同一件事别让四个条件凑齐。三、STL、智能指针与其他锁3.1 STL 容器不是线程安全的1. 默认不加锁STL 容器默认不加任何锁——库追求极致性能锁的开销宁可不付多线程并发读写同一个容器保护责任全在使用者库不知道使用场景的并发模式替用户加锁反而处处添堵STL 的选择是性能优先、安全自理——容器跨线程用锁自己配。3.2 智能指针的线程安全性1. unique_ptr 不进这个问题只在当前代码块内生效出了作用域自动析构所有权独一份不可拷贝、没有引用计数不存在多方共改的问题2. shared_ptr 的引用计数是原子的引用计数被所有拷贝共享读写标准库用原子操作CAS保证增减一步到位——多个线程各自拷贝、析构自己的副本安全边界要分清计数原子只保计数这一层——同一个 shared_ptr 对象本身的并发读写、所指对象内部的并发访问都不在保内shared_ptr 是局部线程安全——计数这一层交给原子操作所指对象那一层还得自己加锁。3.3 悲观锁、乐观锁、CAS、自旋锁、读写锁1. 五种锁的定位悲观锁取数据前先加锁假设别人一定会改乐观锁不加锁更新前用版本号或 CAS 校验没被改过CAS比较当前值相等才写入新值不等就重试——一条原子指令的语义自旋锁抢不到就原地空转重试不睡——与互斥锁的差别就在等锁的姿势互斥锁睡回去自旋锁站着等读写锁读共享、写独占读多写少的场景放开并发度自旋适合临界区极短的场景——等的时间比睡一觉的切换开销还短站着等才划算临界区一长自旋就成纯烧 CPU。四、文末面试题4.1 推导题1. 为什么线程安全不等于可重入用一把锁说清。答推导加锁保证多线程并发进入时一次只进去一个数据竞争被挡在锁外线程安全到手但重入要求同一条执行流第二次进入时依然正确而第二次进入撞上的是自己没解开的锁卡死在 lock 上等一个永远等不到的解锁。前者说的是并发互斥后者说的是重复进入——锁能解决前者解决不了后者所以线程安全买得到、可重入买不到可重入只能是线程安全的子集。2. 「四个必要条件」和「破坏任意一个即可避免」是什么逻辑关系答推导必要条件的含义是缺一不可——死锁要成立互斥、请求与保持、不剥夺、循环等待必须同时成立。逆否过来就是任意一条不成立死锁必然锁不成。所以避免死锁不需要四条全破破坏任意一条就够。工程上挑循环等待下手的原因也在这互斥是锁的本职动不得请求与保持和不剥夺要改分配协议唯独加锁顺序一致只改次序不动语义代价最小。3. 两个线程两把锁交叉申请推演死锁成型的全过程。答推导线程一锁 A 成功线程二锁 B 成功两边各持一把线程一申请 B被线程二持有睡下且不放手 A线程二申请 A被线程一持有也睡下且不放手 B。此时互斥天然成立请求与保持、不剥夺、循环等待三条同时凑齐四条件全部满足——能解开对方的动作分别被对方的锁挡着双方永久等待死锁成型。4. 加锁顺序一致为什么能破坏循环等待答推导循环等待要求等待关系头尾相接成环。规定所有线程都先 A 后 B 之后等 B 的人必先持有 A而等 A 的人按顺序规定手里一把锁都没有——他没有任何东西可以被别人等成不了环上的一环环从第一环就断。等待关系被排成一条单向直线头尾永远接不上。5. malloc/free 为什么不可重入给它加一把锁之后可重入了吗答推导malloc 用全局链表管理堆——空闲块挂在全局结构上申请和释放都要改链。第一次调用改链改到一半被信号打断处理函数里再进 malloc读到的是半新不旧的链表插节点可能把链插丢甚至插成环。加锁之后多线程并发安全了但信号重入时第二次调用撞上自己没解的锁——和上一题同一个结局线程安全到手可重入没到手。6. shared_ptr 的引用计数线程安全性从哪来答推导引用计数被多个线程同时增减而 /-- 是读、改、写三步非原子线程八的 ticket 已经证明过三步操作会被切走。加互斥锁能保安全但太重——标准库用原子操作CAS一步到位比较当前值、相等才替换成新值不等就重读重试。计数原子了各线程拷贝析构各自的副本就安全但它只保计数这一层所指对象的并发访问还得自己加锁。4.2 真题1. 死锁产生的四个必要条件是互斥、 、环路等待和不剥夺。选项释放和阻塞、请求和阻塞、请求和保持、请求和释放答推导 · 已对照解析转述正确答案是请求和保持——进程因请求资源被阻塞时对已获得的资源保持不放「请求」和「保持」两个动作缺一不可。四个选项全带「请求」差别全在后半截这题考的就是条件名里每个字的含义。【真题·转述自 牛客网题库】2. 请你说说互斥锁和自旋锁答推导 · 已对照解析转述加锁失败时互斥锁用线程切换应对——挂进等待队列睡回去自旋锁用忙等待应对——原地循环重试不放弃 CPU。互斥锁适合持锁时间长、竞争激烈的场景自旋锁适合临界区极短的场景。两者是锁的最基本处理方式更高级的锁都基于二者之一实现——读写锁既可以基于互斥锁也可以基于自旋锁。【真题·转述自 牛客网面经题】结语线程安全看执行流、可重入看函数死锁记牢四个必要条件——破坏任意一条就锁不成。Linux 系统篇到此收官评论区聊聊你排查过的死锁现场觉得有用点个赞。

相关新闻

247、【AI】【模型部署】基座模型研究:RoPE 用在前向还是反向——训练、推理与 KV cache

247、【AI】【模型部署】基座模型研究:RoPE 用在前向还是反向——训练、推理与 KV cache

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除 标题 247、【AI】【模型部署】基座模型研究&a…

2026/10/12 4:47:50 阅读更多 →
MATLAB调试实战:报错定位、断点使用与性能优化技巧

MATLAB调试实战:报错定位、断点使用与性能优化技巧

MATLAB跑着跑着突然甩出一段红色报错,绝大多数人的第一反应是:哪一行出问题了?我处理过很多不同类型的数据处理项目,一个很明显的现象是——大家把报错当作“敌人”,恨不得立刻消灭它,却很少有人愿意先花一…

2026/10/12 4:46:50 阅读更多 →
手机生产厂家口碑评估:绕开榜单,用公开信息交叉验证

手机生产厂家口碑评估:绕开榜单,用公开信息交叉验证

每年到了新的产业周期,我都会收到一两张类似的榜单截图。标题通常是“2026年某制造业重镇某头部手机品牌生产厂家TOP5”,配一个看似专业的排序和几句像模像样的评语。来问的人都想知道同一件事:这里面,到底哪家最值得信任&#xf…

2026/10/12 4:46:50 阅读更多 →

最新新闻

AI Agent记忆层:用mem0构建跨会话的长期记忆系统

AI Agent记忆层:用mem0构建跨会话的长期记忆系统

1. 先说清楚:Agent缺的不是智商,是记性这两年做AI Agent项目的人应该都有同感:模型本身的推理能力已经很强了,真正拖后腿的反而是“记忆”。同一个用户第二次来提问,Agent完全不记得他上次说过什么;用户昨天…

2026/10/12 5:37:18 阅读更多 →
下班关机上班远程唤:群晖 WoL 网络唤醒与电源恢复两开关的运维账

下班关机上班远程唤:群晖 WoL 网络唤醒与电源恢复两开关的运维账

引言 不是每台 NAS 都值得 7x24 开机:门店、小分支、测试机,下班关机、用时再开,一年省下的电费和硬盘寿命都实实在在。但「开机」这个动作如果每次都要人跑到机器跟前按电源键,省电就省成了麻烦。DSM 的「控制面板 > 硬件和电…

2026/10/12 5:37:18 阅读更多 →
大模型成本管控实战:从预算、计量到优化的全链路指南

大模型成本管控实战:从预算、计量到优化的全链路指南

上线大模型只是第一步,用好AI离不开完整的成本管控。这句话我在多个项目里反复验证过。早两年大家聊大模型,问得最多的是“能不能做、效果行不行”,现在技术底座成熟了,问题变成了“做出来之后,钱怎么花”。我见过不止…

2026/10/12 5:37:18 阅读更多 →
告别随机输出:用“AI调研队”把调研质量从碰运气变稳定

告别随机输出:用“AI调研队”把调研质量从碰运气变稳定

1. 为什么是组队,而不是堆更多提示词:问题出在工序上1.1 一次让人抓狂的"标准提问"我猜很多人和我一样,最开始用AI做调研是这么干的:打开对话框,输入"帮我调研一下某某行业的现状,要全面、要…

2026/10/12 5:37:18 阅读更多 →
用C++手搓肉鸽游戏:随机地图生成与状态机实战

用C++手搓肉鸽游戏:随机地图生成与状态机实战

简介:这是一份基于C与EasyX图形库开发的肉鸽游戏《Slime-Hunter》中期版本源码及可执行文件,面向初学C或对游戏开发感兴趣的读者,可用于课程设计参考或图形编程入门实践。资源共531个文件,压缩包约95.16MB,其中包含277…

2026/10/12 5:37:18 阅读更多 →
第 15 章 政务/制造业落地案例:用 TaoToken 统一 Key 跑通 DeepSeek-V3 信创推理链路

第 15 章 政务/制造业落地案例:用 TaoToken 统一 Key 跑通 DeepSeek-V3 信创推理链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 5:36:17 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →