多线程之互斥锁与死锁:一个真实死锁bug让我排查了整整两天
上篇把线程创建与管理聊了。今天聊多线程里最让人头疼的问题——数据竞争和死锁。先讲个真实的bug故事。之前做一个多传感器融合的项目激光雷达和IMU的数据分别由两个线程读取融合之后发布。上线测试的时候程序跑着跑着就卡死了——不是崩溃是卡住不动了。没有任何错误日志就是所有线程都不干活了。排查了两天最后发现是死锁。问题出在哪两个线程各自持有两把锁但加锁顺序不一样// 线程A先锁lidar_mutex再锁fusion_mutex void lidarThread() { while (running_) { lock_guardmutex lock1(lidar_mutex_); auto scan latestScan_; lock_guardmutex lock2(fusion_mutex_); // 等fusion_mutex fusionData_.lidar scan; } } // 线程B先锁fusion_mutex再锁imu_mutex void imuThread() { while (running_) { lock_guardmutex lock1(imu_mutex_); auto imu latestImu_; lock_guardmutex lock2(fusion_mutex_); // 也等fusion_mutex fusionData_.imu imu; } }某个时刻线程A拿了lidar_mutex_等fusion_mutex_线程B拿了imu_mutex_也要等fusion_mutex_——等等这其实不是死锁只是竞争。真正的死锁是互相等对方手里的锁。让我还原一下当时的真实场景// 真正的死锁场景 void threadA() { mutex1_.lock(); // A拿了mutex1 // ... 做一些事 mutex2_.lock(); // A等mutex2 } void threadB() { mutex2_.lock(); // B拿了mutex2 // ... 做一些事 mutex1_.lock(); // B等mutex1 // 死锁A等B的mutex2B等A的mutex1 }mutex的基本用法C11提供了std::mutex来解决数据竞争。基本用法很简单mutex mtx; int sharedCounter 0; void increment() { mtx.lock(); sharedCounter; // 临界区同一时间只有一个线程能访问 mtx.unlock(); }但直接用lock/unlock很危险——如果临界区里抛了异常unlock不会被执行锁就永远不会释放。所以C推荐使用RAII方式的lock_guard或unique_lockvoid increment() { lock_guardmutex lock(mtx); // 构造时加锁 sharedCounter; // 析构时自动解锁即使抛异常也安全 }lock_guard是最简单的锁包装——构造加锁析构解锁没有别的选项。unique_lock更灵活支持延迟加锁、手动解锁、try_lockvoid flexibleLock() { unique_lockmutex lock(mtx, defer_lock); // 先不锁 // 做一些不需要锁的事 prepareData(); lock.lock(); // 现在才加锁 criticalSection(); lock.unlock(); // 可以手动解锁 // 非阻塞尝试 if (lock.try_lock()) { // 拿到锁了 } else { // 没拿到做别的事 } }死锁的四个必要条件死锁的发生需要同时满足四个条件互斥——资源同一时间只能被一个线程持有。锁天然是互斥的这个没法消除。持有并等待——线程持有一把锁的同时等待另一把锁。不可抢占——锁不能被强制从持有者手里夺走。循环等待——线程A等B持有的锁B等C持有的锁C又等A持有的锁。要防止死锁只要打破其中任意一个条件就行。最实用的做法是打破循环等待——让所有线程按相同的顺序加锁。// 解决方案统一加锁顺序 void threadA() { lock_guardmutex lock1(mutex1_); // 先锁1 lock_guardmutex lock2(mutex2_); // 再锁2 } void threadB() { lock_guardmutex lock1(mutex1_); // 同样的顺序 lock_guardmutex lock2(mutex2_); // 先锁1再锁2 }C17还提供了一个更方便的工具——std::lock可以同时锁多把锁而不死锁void transferMoney(Account from, Account to, double amount) { // std::lock同时锁两个mutex内部用try-and-back-off避免死锁 lock(from.mutex_, to.mutex_); lock_guardmutex lockFrom(from.mutex_, adopt_lock); lock_guardmutex lockTo(to.mutex_, adopt_lock); from.balance_ - amount; to.balance_ amount; }机器人开发中的典型数据竞争在机器人系统里数据竞争最常出现在传感器数据和共享状态上。class SensorFusion { mutex mtx_; Pose currentPose_; public: void updateFromImu(const ImuData data) { lock_guardmutex lock(mtx_); currentPose_ integrateImu(data); } void updateFromLidar(const LidarScan scan) { lock_guardmutex lock(mtx_); currentPose_ matchLidar(scan); } Pose getPose() { lock_guardmutex lock(mtx_); return currentPose_; } };这个设计是对的但要注意锁的粒度。如果临界区太大锁住了大量计算其他线程就得等着并行度下降。如果临界区太小只锁了一个赋值可能保护不够。经验法则是只锁住访问共享数据的那几行代码计算逻辑放在锁外面。面试中的关键考点mutex和spinlock有什么区别mutex拿不到锁时会阻塞线程让出CPUspinlock拿不到锁时会一直循环尝试忙等不让出CPU。mutex适合临界区较长的场景spinlock适合临界区极短几个指令的场景。recursive_mutex是什么允许同一个线程对同一把mutex多次加锁而不会死锁。但一般不推荐用——如果你发现需要recursive_mutex多半是设计有问题。怎么排查死锁Linux下可以用gdb attach到进程看每个线程的调用栈找出各自持有什么锁、在等什么锁。工具层面可以用ThreadSanitizerTSan编译时加-fsanitizethread就能检测潜在的数据竞争。给正在准备面试的你互斥锁和死锁是面试高频考点。基本的mutex用法必须熟练lock_guard和unique_lock的区别要能讲清楚。死锁的四个条件和预防方法也要能说出来。最重要的是写代码的时候要有锁的意识。拿到一把锁的时候脑子里就要想清楚——这把锁和其他锁有没有顺序冲突会不会和其他线程的加锁顺序形成环路养成这个好习惯能帮你避免绝大多数死锁问题在面试和实际项目开发中都非常有用。下篇聊条件变量——生产者消费者模型在传感器数据流中的实际应用。这是多线程协作的核心机制和重要基础。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第54篇 多线程之线程创建与管理——机器人系统中为什么必须用多线程 下一篇预告第56篇 多线程之条件变量——生产者消费者模型在传感器数据中的应用有任何问题欢迎评论区留言我会尽量回复。

相关新闻

Tiva TM4C123BE6PM管脚功能详解与配置实战

Tiva TM4C123BE6PM管脚功能详解与配置实战

1. 项目概述与核心价值对于任何一位嵌入式硬件工程师或单片机开发者而言,拿到一颗新的微控制器(MCU)后,第一件要紧事就是“啃”数据手册里的管脚定义表。这看似枯燥的表格,却是连接芯片内部强大功能与外部现实世界的唯…

2026/10/15 4:16:17 阅读更多 →
多线程之线程创建与管理:面试官问“机器人系统为什么必须用多线程”

多线程之线程创建与管理:面试官问“机器人系统为什么必须用多线程”

从今天开始进入多线程板块。这块内容在C面试里分量很重——互斥锁、死锁、条件变量、原子操作、线程池,一连串的话题,每个都能聊一篇。先从最基础的开始:线程的创建与管理。为什么要用多线程?这个问题在机器人开发里答案特别明确&…

2026/10/14 6:54:12 阅读更多 →
2024年7月AI大模型技术报告:Gemini、Grok、GPT最新突破与编程模型格局

2024年7月AI大模型技术报告:Gemini、Grok、GPT最新突破与编程模型格局

最近在AI领域真是热闹非凡,各大厂商都在密集发布新模型和功能更新。如果你正在关注AI大模型的最新进展,特别是Gemini、Grok和GPT系列的最新动态,那么本文正好为你整理了一份完整的7月AI技术发展报告。本文将详细解析Gemini 3.5 Pro的发布预测…

2026/10/15 3:01:22 阅读更多 →

最新新闻

SpringBoot双模版电商系统设计与实现

SpringBoot双模版电商系统设计与实现

1. 项目概述:双模版电商系统的技术实现 这个SpringBoot零食商城系统是一个典型的轻量级电商解决方案,最显著的特点是采用了模块化设计,同时支持零食商城和花店管理两种业务模式。我在实际部署测试中发现,系统通过配置开关即可切换…

2026/10/15 13:47:17 阅读更多 →
DeepSeek模型本地部署与推理避坑指南

DeepSeek模型本地部署与推理避坑指南

简介:本资源是一份面向AI从业者、技术管理者与高校研究者的DeepSeek大模型内部科普培训材料,系统解析国产高性能大模型的技术架构、能力边界与落地路径。PPTX格式的单文件课件(27.18MB)结构清晰,涵盖DeepSeek-V3&#…

2026/10/15 13:47:17 阅读更多 →
Jev 估值 75 亿美元后,微软火速反击:数据之战下 API 通道如何选?TaoToken 统一 Key 实测

Jev 估值 75 亿美元后,微软火速反击:数据之战下 API 通道如何选?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/15 13:47:17 阅读更多 →
物流中转站选址规划与运营实操:从选址到成本控制的全流程指南

物流中转站选址规划与运营实操:从选址到成本控制的全流程指南

1. 物流中转站选址与规划的核心逻辑1.1 为什么中转站选址是生死攸关的第一步做物流这行十几年,我见过太多项目死在选址上。一个物流中转站建起来,土地成本、建设成本、设备投入加起来动辄几百万甚至上千万,一旦位置选错,后面怎么优…

2026/10/15 13:47:17 阅读更多 →
异常检测实战:从统计基线到自适应建模的分层方法

异常检测实战:从统计基线到自适应建模的分层方法

1. 这不是“找bug”,而是让机器学会识别“不对劲”的直觉你有没有过这种感觉:在一堆整齐划一的数据里,突然冒出一个格格不入的点——它数值大得离谱,位置偏得诡异,行为模式完全不像其他样本?比如某天服务器…

2026/10/15 13:47:17 阅读更多 →
ponytail:把终端命令变成可复用技能的高效自动化工具

ponytail:把终端命令变成可复用技能的高效自动化工具

ponytail。第一次在同事的终端里看到这个词,我还以为是某个桌面对弈壁纸的花名。直到他现场敲了两行命令,把原本我得翻半天历史记录、拼接七八条命令才能完成的日志分析,变成了问候式的一行pt log --path access.log --top 5,我才…

2026/10/15 13:46:16 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/15 9:49:14 阅读更多 →