当前位置: 首页 > news >正文

嵌入式Linux多线程防止死锁

一、并发的本质

1. 并发≠并行

先弄清楚两个概念:

  • 并发(Concurrency):多个任务在时间片上交替执行,宏观上"同时",微观上是串行切换

  • 并行(Parallelism):多个任务在多个CPU核心上真正同时执行

在单核ARM芯片上,你写的多线程代码是并发;在多核处理器上,才可能是并行。但无论哪种情况,只要存在共享资源,就必须面对竞态条件(Race Condition)

2. 竞态的根源

竞态的根源是多个执行流对共享资源的访问顺序不确定。解决它,就是多线程编程的核心。

访问顺序不确定有如下几种情况:

(1)一行C代码≠一条指令

看这段常见的计数器代码:

counter++看起来简单,编译后实际是三条指令:

LOAD counter → 寄存器 ADD 寄存器 + 1 STORE 寄存器 → counter

两个线程各执行10万次,理论结果是20万。但实测往往只有13-18万。原因就是两个线程的指令可能"交叉执行"。

(2)编译器和CPU乱序执行

为了优化性能,编译器可能重排指令顺序,CPU也有乱序执行机制。我们写的代码顺序,不一定是实际执行顺序。

(3)多核CPU各有各的缓存

每个核心都有自己的L1/L2 Cache,对同一内存地址的修改,不会立即对其他核心可见。这叫缓存一致性问题

二、POSIX线程库三大同步原语

POSIX线程库提供了几种同步机制,互斥锁、条件变量、读写锁。

2.1 互斥锁

互斥锁的语义很简单:同一时刻只有一个线程能持有锁

需要特别注意的是:锁的粒度要小

如果误把整个业务逻辑都放在锁里面,结果多线程变成了排队执行,性能还不如单线程。

错误示范:锁的粒度太大的例子

正确做法:只锁共享数据的访问,不锁计算逻辑

2.2 条件变量

生产者线程产生数据,消费者线程处理数据。消费者怎么知道"有数据了"?这时候可以使用条件变量。

条件变量正是解决线程等待-通知场景的最优解——它能让线程在条件不满足时休眠,条件满足时精准唤醒,既保证响应速度,又能最大化降低CPU占用。

错误方案:轮询方式

正确方案:使用条件变量

使用条件变量的典型步骤:

等待方步骤

  1. 加互斥锁(pthread_mutex_lock);

  2. 循环检查条件(while(condition == false));

  3. 条件不满足时,调用pthread_cond_wait休眠;

  4. 被唤醒后,重新检查条件,执行业务逻辑;

  5. 解锁互斥锁(pthread_mutex_unlock)。

通知方步骤

  1. 加互斥锁(pthread_mutex_lock);

  2. 修改条件(如:设置flag为true、添加数据到队列);

  3. 发送通知(pthread_cond_signal或pthread_cond_broadcast);

  4. 解锁互斥锁(pthread_mutex_unlock)。

代码如:

为什么必须用while而不是if?

因为存在虚假唤醒"(spurious wakeup)——线程可能在没有收到signal的情况下被唤醒。这是POSIX标准允许的行为,用while可以再次检查条件。

2.3 读写锁

如果你的场景是"90%读、10%写",用互斥锁太浪费——读操作之间本不需要互斥。

下图对比三种同步原语的适用场景:

三、死锁

比方说两个线程甚至多个线程,需要获取两个临界值,也就是需要两个锁

死锁是多线程编程最经典的问题。它的四个必要条件(Coffman条件):

  1. 互斥:资源不能共享

  2. 持有并等待:持有一个锁的同时等待另一个锁

  3. 不可抢占:锁不能被强制释放

  4. 循环等待:A等B,B等A

破坏任意一个条件就能预防死锁。实践中最有效的是破坏"循环等待":规定加锁顺序

经典AB-BA死锁例子如:

以上程序卡死,CPU占用为0。这是经典的AB-BA死锁模式。两个线程以相反的顺序获取两把锁,在特定时序下互相等待,形成死锁。

执行时序图

为什么发生死锁呢?

线程1和线程2同时启动,线程1获取了锁A,又想要获取锁B,此时线程2获取了锁B,又想要获取锁A,此时两个线程都不能获取第二个锁,就形成了死锁

如何解决呢?两个线程获取锁的顺序一定要一致,比如一开始都去获取锁A,当一个线程获取到了锁A,另外一个线程就会进入等待,此时不会形成死锁

修复以上死锁问题:统一加锁顺序

释放锁的时候都是先B后A

规避死锁问题的工程实践建议

  • 在代码规范中明确锁的层级顺序

  • 使用pthread_mutex_trylock实现超时机制

  • 开发阶段启用死锁检测工具(如Helgrind、ThreadSanitizer)

四、总结

三条核心原则

  1. 最小化共享:能不共享就不共享,能用消息传递就不用共享内存

  2. 最小化临界区:锁的粒度越小越好,只保护数据访问,不保护计算逻辑

  3. 统一加锁顺序:从根源上避免死锁

http://www.cnnetsun.cn/news/158680.html

相关文章:

  • 28nm以下工艺PMIC设计雷区:LOD、WPE、HKMG如何悄悄毁掉你的LDO?
  • Abaqus水力压裂模拟:基于Cohesive单元与XFEM的方法研究
  • 44、COMSOL模拟二维裂隙流压裂水平井裂缝性油藏离散裂缝网络模型COMSOL数值模拟案例
  • 今天咱们来聊聊ReliefF算法,一个在分类数据特征选择中相当实用的工具。废话不多说,直接上代码,边看边聊
  • MATLAB R2018A环境下的液相色谱信号自动调优降噪算法——交叉验证作为参数调节器
  • 计算机Java毕设实战-基于springboot的足球训练营系统的设计与实现设计与实现基于SpringBoot的青训足球综合运营平台设计与实现 【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 2025年软件测试技术发展趋势与从业者应对策略
  • 电驱动(电机+电控)开发验证方法与技巧的高清视频教程,深入讲解精细技术,掌握实用技巧
  • 每天24小时的电价(元/kWh)
  • C#编程下的自定义控件与OpenCVSharp结合应用:卡尺测距功能实现
  • NGBoost-shap方法回归任务,由斯坦福吴恩达团队提出,属于集成模型的一种2019年提出的
  • Langchain-Chatchat Kubernetes集群部署策略
  • Langchain-Chatchat日志监控与性能分析最佳实践
  • Langchain-Chatchat模型微调指南:适配垂直领域任务
  • 如何配置IPv6静态路由?解决企业网络难题
  • 【Linux网络基础】详解 TCP 面向连接 vs UDP 无连接
  • Langchain-Chatchat如何评估问答质量?指标体系构建
  • springboot在线教育系统(11528)
  • 测了多款AI自动生成PPT工具,真正能用的不到一半
  • springboot星之语明星周边产品销售网站的设计与实现(11529)
  • 毕设救星:Spring Boot + Neo4j 打造“医疗知识问答”——基于知识图谱的智能导诊平台
  • 华为网络设备基本配置命令
  • 志同道合交友网站毕业论文+PPT(附源代码+演示视频)
  • 【Java 25 LTS六大核心特性】
  • Langchain-Chatchat助力医疗文档智能检索与问答
  • Langchain-Chatchat如何实现文档相似度比对?查重与去重依据
  • java学习--String和StringBuffer互转
  • 如何用Langchain-Chatchat实现本地化AI智能问答?
  • Langchain-Chatchat如何处理多义词歧义?上下文感知消歧算法
  • Langchain-Chatchat如何实现文档访问统计?了解知识使用情况