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

Linux线程同步与互斥(六):线程安全、可重入与死锁

为什么有些函数不能在多线程环境下使用?为什么加了锁的程序还是可能崩溃?什么是死锁?STL容器线程安全吗?


一、线程安全和重入话题

1.1 线程安全

定义:多个线程同时访问一个函数或对象,不会出现不确定的结果,即使没有额外的同步机制(或者有正确的同步),也能正确执行。

通俗理解:你的代码不会因为多线程调度而产生“数据损坏”或“逻辑错误”。

例子

  • 全局变量int counter两个线程同时counter++→ 不安全。

  • 如果对counter加锁保护 → 安全。

翻译成大白话:

多个线程一起跑

  • 一起读
  • 一起写
  • 一起改

结果永远正确,不会出现负数、错乱、覆盖、脏数据 → 就是线程安全!

1.2 可重入

定义函数可以被多个执行流同时进入,而不会产生数据错乱。这里的“执行流”可以是多个线程,也可以是同一个线程在执行过程中被信号处理函数打断后再次进入该函数

通俗理解:函数即使被“重入”(比如刚执行到一半,又再次被调用),也能正确运行。

典型场景:信号处理函数中调用的函数必须是可重入的,因为信号可能在主程序的任意位置触发,导致同一个函数被重复进入。

小白话

函数跑到一半,被 “中途插队” 再进一次!

两种重入场景:

  1. 多线程重入(两个线程同时进同一个函数)
  2. 信号打断重入(一个线程被信号中断,又进一次)

可重入函数的特点

  • 不使用全局变量、静态变量
  • 不调用 malloc/free
  • 不调用不可重入函数
  • 所有变量都是局部变量(栈上)

1.3 什么时候线程不安全?

只要出现下面任意一种,一定不安全

  1. 多个线程访问共享资源(全局变量 / 静态变量)
  2. 又读又写
  3. 没有加锁保护

满足这 3 条 = 线程不安全!

1.4 总结(重点)

不要被上面的绕口令式的话语吓唬住,你只需要指定,其实对应概念说的都是一回事

最简单的 3 句话

  1. 可重入函数一定是线程安全的
  2. 线程安全函数不一定是可重入的
  3. 使用了全局变量但加了锁 → 线程安全,但不可重入

为什么加锁的函数是线程安全,但不可重入?

void func() { lock(); // 加锁 ... // 中途被信号打断 unlock(); // 没执行到 }

信号来了 → 又进一次 func ()→ 再次 lock ()→ 锁已经被持有 →死锁!

所以:

✅ 线程安全(多线程用没问题)

❌ 不可重入(信号 / 递归重入会死锁)


二、常见锁概念

2.1 死锁

两个或多个线程互相持有对方需要的资源,并且都不释放,导致所有线程永久阻塞。

你拿着我要的,我拿着你要的,互相不释放 →永远卡住

假设现在的线程A、线程B必须同时拥有锁1和锁2,才能进行后续资源的访问

// 线程 A // 线程 B pthread_mutex_lock(&mutex1); pthread_mutex_lock(&mutex2); pthread_mutex_lock(&mutex2); pthread_mutex_lock(&mutex1);

申请一把锁式原子的,但是申请两把锁就不一定了。

造成的结果是:

2.2 死锁的四个必要条件

互斥条件 ,一个资源每次只能被一个执行流使用

请求与保持条件:一个执行流因请求资源而阻塞时,对已经获得的资源保持不放

不剥夺条件:一个执行流已经获得的资源,在未使用完之前,不得强行剥夺

循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

条件说明
互斥资源一次只能被一个线程占用
请求与保持线程持有资源的同时请求其他资源
不剥夺线程不释放已占有的资源
循环等待存在等待环路:A等B,B等A

2.3 避免死锁

  • 破坏“请求与保持”:一次性申请所有资源(std::lock或同时加多把锁)。

  • 破坏“循环等待”:所有线程按固定顺序加锁(例如总是先锁 mutex1 再锁 mutex2)。

  • 破坏“不剥夺”:使用pthread_mutex_trylock,失败时释放已有的锁。

  • 使用超时机制pthread_mutex_timedlock

示例:固定顺序加锁

// 线程 A 和 B 都先锁 mutex1,再锁 mutex2 pthread_mutex_lock(&mutex1); pthread_mutex_lock(&mutex2);

示例:使用std::lock一次锁多把锁(C++11)

std::lock(mutex1, mutex2); // 不会死锁 std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock); std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock);

2.4 常见锁概念(简介)

锁类型特点适用场景
互斥锁独占锁,阻塞等待保护普通临界区
自旋锁忙等待,不释放CPU临界区极短,避免上下文切换
读写锁多读单写读多写少
乐观锁不加锁,更新前检查版本数据库、并发控制
悲观锁每次访问都加锁冲突概率高
CAS(Compare And Swap)原子比较并交换无锁编程基础

悲观锁 vs 乐观锁:悲观锁假设冲突会发生,提前加锁;乐观锁假设冲突很少,更新时检测,失败则重试。

避免死锁算法:死锁检测算法、银行家算法


三、STL、智能指针和线程安全

3.1 STL容器是否线程安全?

不是。STL容器的设计目标是极致性能,没有内部同步机制。多线程环境下:

  • 多个线程同时读取同一个容器是安全的。

  • 只要有一个线程写,就必须由用户加锁保护。

std::vector<int> vec; // 多线程 push_back 需加锁

3.2 智能指针是否线程安全?

  • std::unique_ptr:不涉及共享,完全在线程栈上,安全。

  • std::shared_ptr:引用计数是原子操作,所以多个线程同时拷贝、析构shared_ptr是安全的(计数不会出错)。但是,指向的对象本身不是线程安全的,如果多个线程通过shared_ptr修改同一个对象,仍需要加锁

std::shared_ptr<int> sp = std::make_shared<int>(10); // 多个线程拷贝 sp 或让 sp 析构,安全 // 但 *sp = 20; 不安全,需保护
http://www.jsqmd.com/news/707833/

相关文章:

  • NAD+哪个产品最好?2026年度NAD+牌子榜单:从技术到成分全面横评,这10款值得收藏! - 资讯焦点
  • 2026 arXiv | HM-Bench:高光谱遥感领域多模态大模型综合基准测试
  • Qianfan-OCR-4B算法原理浅析:从CNN到端到端文档理解
  • STM32CubeIDE定时器PWM实战:从驱动舵机到控制电机转速,一份配置通吃
  • 南宁汽车抵押典当全指南:从选机构到当天拿钱,这篇说透 - 资讯焦点
  • vs2019添加Qt的pri文件
  • 国产替代SYPS-2-252+ 成都恒利泰HT-SYPS-2-252+
  • 实例化管理化技术对象池与依赖注入
  • 工学毕业论文怎么用ai快速生成?这款工具强烈推荐! - 掌桥科研-AI论文写作
  • 从NeRF到NeuS:手把手教你用PyTorch复现SDF体渲染,搞定多视角三维重建
  • Qt项目实战:手把手教你用C++实现农历转换(附完整源码解析)
  • AMD Ryzen终极性能调优指南:SMUDebugTool免费开源工具完全解析
  • 国内首份MCP 2026车载系统适配白皮书(含12家Tier1实测数据、CAN FD带宽压测报告、功能安全ASIL-B映射表)
  • nad+口服哪个牌子好?2026优质NAD+品牌口碑实测榜推荐:口碑吸收性价比全解析+全场景抗衰选购指南 - 资讯焦点
  • Mprpc服务框架的实现
  • 【李沐 | 动手学深度学习】17 深度学习硬件:CPU 和 GPU
  • 国内最推荐的双壁热缩管批发厂家有哪些?2026年市场选择前五排名 - 十大品牌榜
  • 从Cortex-M到Cortex-A:内存屏障(DMB/DSB/ISB)的使用差异与迁移心得
  • 从图像分类到目标检测:聊聊CNN平移不变性在实际CV任务中的‘得与失’
  • 【Vercel实用Skill】json-render-react-native 技能
  • 银泰百货卡变现技巧:教你如何卖出最高价! - 团团收购物卡回收
  • 佛山定制楼梯技术分享:适配、安全与性价比全解析 - 资讯焦点
  • 3.1 建筑给水排水与供暖工程施工技术
  • 终极指南:30倍提速!百度网盘直链解析工具轻松突破限速
  • 基础数据结构——栈和队列
  • 04 | 笔试算法题:凑最长不重复字符串的数目问题
  • 告别台架依赖:SkyEyeCANoe实现汽车CAN通信软件在环验证
  • G-Helper风扇控制终极指南:如何为你的ROG笔记本定制完美散热方案
  • 中山定制楼梯品牌怎么选?技术维度拆解靠谱标准 - 资讯焦点
  • .NET SqlSugar 仓储、工作单元、服务层