发布时间:2026/9/1 4:51:01
Linux 多线程——线程互斥:从抢票问题到 Mutex 底层原理 一、线程为什么需要互斥同一进程中的多个线程共享进程的地址空间因此多个线程可能同时访问同一份数据。例如int ticket 100;如果创建多个线程共同执行售票逻辑if (ticket 0) { printf(sell ticket:%d\n, ticket); ticket--; }多个线程都会访问和修改同一个ticket。如果这些线程的执行过程发生交叉就可能造成数据不一致。因此在学习互斥锁之前需要先理解几个基本概念。讲义首先给出了共享资源、临界资源、临界区、互斥和原子性的概念。1. 共享资源可以被多个执行流共同访问的资源就是共享资源。例如int ticket 100;多个线程都可以访问ticket所以ticket是共享资源。2. 临界资源需要受到保护、不能被多个线程随意并发访问的共享资源称为临界资源。例如售票程序中的ticket就是临界资源。3. 临界区线程中访问临界资源的那部分代码称为临界区。例如if (ticket 0) { printf(ticket %d\n, ticket); ticket--; }这段代码访问和修改了ticket所以它属于临界区。简单记ticket ↓ 临界资源 访问 ticket 的代码 ↓ 临界区4. 互斥互斥指的是同一时刻只允许一个执行流进入临界区访问临界资源。例如线程1 ──→ 进入临界区 线程2 ──→ 等待 线程3 ──→ 等待 线程4 ──→ 等待需要特别注意互斥并不是让整个程序变成单线程而只是保证临界区同一时刻只能由一个线程执行。5. 原子性原子操作指的是一个操作在执行过程中不能被其他执行流看到“执行一半”的状态。简单理解要么完全没执行 要么已经全部执行完成不存在中间状态。二、为什么抢票程序会出问题先看没有加锁的版本int ticket 100; void* route(void* arg) { char* id (char*)arg; while (true) { if (ticket 0) { usleep(1000); printf(%s sells ticket:%d\n, id, ticket); ticket--; } else { break; } } return nullptr; }创建多个线程同时运行后可能出现thread 4 sells ticket:1 thread 2 sells ticket:0 thread 1 sells ticket:-1 thread 3 sells ticket:-2讲义中的抢票实验就出现了票数变成0、-1、-2的情况。问题的关键就在ticket--;看起来只有一行但它并不是原子操作。三、ticket--为什么不是原子的从底层汇编角度看ticket--大致可以分成三步mov ticket, %eax sub $1, %eax mov %eax, ticket分别对应1. load 从内存读取 ticket 到寄存器 2. update 寄存器中的值减 1 3. store 把结果重新写回内存讲义也正是通过load → update → store三个步骤说明--ticket不是原子操作。所以 CPU 完全可能在这三步之间发生线程切换。假设ticket 1线程 A读取 ticket 1此时 CPU 把 A 切走。线程 B 开始运行也读取ticket 1于是线程A认为还有1张票 线程B也认为还有1张票最终两个线程都可能执行售票。问题的本质就是多个线程对共享数据的操作发生了交叉。四、为什么if(ticket 0)也没用有人可能会觉得if (ticket 0) { ticket--; }不是已经检查过了吗但是if (ticket 0)和ticket--;并不是一个不可分割的整体。例如线程A 判断 ticket 0 ↓ 成立 ↓ CPU切换 线程B 判断 ticket 0 ↓ 成立 ↓ 卖票并 ticket-- CPU重新切回线程A 线程A继续卖票因此判断时条件成立不代表真正执行修改时条件仍然成立。所以售票中的判断 输出 ticket--必须作为一个整体进行保护。五、使用 mutex 实现线程互斥Linux pthread 库提供了互斥量pthread_mutex_t基本使用结构就是pthread_mutex_lock(mutex); // 临界区 pthread_mutex_unlock(mutex);多个线程竞争同一把锁时mutex │ ┌────────┼────────┐ ↓ ↓ ↓ thread1 thread2 thread3 │ 抢锁成功 │ ↓ 临界区 thread2、thread3等待当一个线程已经进入临界区后其他线程就不能再进入。讲义总结互斥要求时指出临界区执行过程中不能让其他线程进入多个线程同时申请进入临界区时只能有一个线程成功。Linux 中通过互斥量完成这种保护。六、pthread mutex基本接口1. 定义互斥量pthread_mutex_t mutex;2. 初始化动态初始化pthread_mutex_init(mutex, nullptr);完整接口int pthread_mutex_init( pthread_mutex_t* mutex, const pthread_mutexattr_t* attr );学习阶段第二个参数一般使用nullptr也可以静态初始化pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;这两种方式都是讲义介绍的标准初始化方法。3. 加锁pthread_mutex_lock(mutex);如果锁当前没有被其他线程持有锁空闲 ↓ 当前线程抢锁成功 ↓ 进入临界区如果锁已经被其他线程持有锁被占用 ↓ 当前线程抢锁失败 ↓ 阻塞等待讲义指出如果 mutex 已经被其他线程锁定pthread_mutex_lock可能使当前线程进入阻塞状态等待锁被释放。4. 解锁pthread_mutex_unlock(mutex);表示当前线程已经完成临界区操作可以让其他线程继续竞争这把锁。5. 销毁pthread_mutex_destroy(mutex);一般在线程全部结束并且确定后面不会继续使用该 mutex 时销毁。七、使用mutex改造抢票程序核心代码如下int ticket 100; pthread_mutex_t mutex; void* route(void* arg) { const char* id static_castconst char*(arg); while (true) { pthread_mutex_lock(mutex); if (ticket 0) { usleep(1000); printf(%s sells ticket:%d\n, id, ticket); ticket--; pthread_mutex_unlock(mutex); } else { pthread_mutex_unlock(mutex); break; } } return nullptr; }整个过程lock ↓ 判断 ticket ↓ 售票 ↓ ticket-- ↓ unlock讲义中的改进版本也是通过这种方式保护整个售票临界区。这里有一个很重要的细节else { pthread_mutex_unlock(mutex); break; }不能写成else { break; }因为在执行break之前当前线程仍然持有 mutex。如果直接退出线程A获得锁 ↓ 发现没票 ↓ 直接break ↓ 线程退出 ↓ 锁没有释放 ↓ 其他线程一直抢不到锁所以要记住成功 lock 以后最终一定要有对应的 unlock。八、加锁以后线程还能被CPU切走吗当然可以。例如pthread_mutex_lock(mutex); ticket--; pthread_mutex_unlock(mutex);假设线程 A 获得 mutex线程A获得锁 ↓ 开始执行临界区 ↓ CPU把A切走这时候线程 B 开始运行并调用pthread_mutex_lock(mutex);但是 mutex 仍然属于 A因此线程B抢锁失败 ↓ 等待等 A 再次被调度线程A继续运行 ↓ 完成临界区 ↓ unlockB 才有机会继续竞争锁。因此mutex 不是禁止线程调度。mutex 真正保证的是即使持锁线程被 CPU 切换出去其他线程也不能进入同一个临界区。九、为什么锁本身必须依赖原子操作假设我们自己设计一把锁mutex 1锁空闲 mutex 0锁被占用然后if (mutex 1) { mutex 0; }看起来似乎可以抢锁。实际上还是不安全。例如mutex 1 线程A 线程B │ 发现mutex1 发现mutex1 │ mutex0 mutex0两个线程最终都认为我获得锁了问题在于检查 mutex和修改 mutex仍然是两个独立操作。所以锁最重要的一步是“检查锁状态”和“修改锁状态”必须一次性完成。这就需要 CPU 提供原子指令。十、exchange / xchg如何实现互斥讲义介绍了利用swap/exchange原子交换指令实现 mutex 的基本思想。假设mutex 1锁空闲 mutex 0锁被占用简化后的加锁伪代码lock: movb $0, %al xchgb %al, mutex if (%al 0) return 0; 挂起等待; goto lock;首先movb $0, %al表示AL 0然后xchgb %al, mutex原子交换AL 和 mutex第一个线程抢锁原来AL 0 mutex 1交换后AL 1 mutex 0AL 1说明 mutex 原来处于空闲状态因此当前线程抢锁成功同时mutex 0表示锁已经被占用。第二个线程抢锁第二个线程同样先执行AL 0但是现在mutex 0交换AL 0 mutex 0于是AL 0说明这把锁之前就已经被其他线程持有。所以抢锁失败 ↓ 等待多个线程竞争时mutex 1 │ 原子 xchg │ ┌──────────┼──────────┐ ↓ ↓ ↓ thread1 thread2 thread3 │ │ │ 成功 失败 失败 │ mutex 0 │ ↓ 临界区所以互斥锁底层最核心的一点就是使用原子操作一次性完成“检查锁状态 修改锁状态”。这样无论多少线程同时竞争也只有一个线程能够成功。十一、为什么unlock可以直接释放锁释放锁时不需要再次竞争。因为执行 unlock 的线程本身已经持有这把锁。所以只需要把mutex 0恢复成mutex 1例如movb $1, mutex过程线程持有mutex ↓ 完成临界区 ↓ mutex 1 ↓ 锁重新空闲 ↓ 唤醒等待线程需要注意被唤醒的线程并不是直接获得锁。它只是重新进入lock ↓ xchg ↓ 重新竞争mutex因此lock负责竞争锁unlock负责释放锁。另外同一个临界资源如果在多个地方被访问都应该遵守同一套加锁规则否则仍然可能发生数据竞争。十二、使用RAII管理互斥锁使用 pthread 时我们经常手动写pthread_mutex_lock(mutex); // 临界区 pthread_mutex_unlock(mutex);最大的风险就是忘记 unlock。例如pthread_mutex_lock(mutex); if (error) { return nullptr; // 忘记解锁 } pthread_mutex_unlock(mutex);因此可以把 mutex 封装起来。讲义中首先封装了Mutex类再通过LockGuard实现 RAII 风格的锁管理。例如class LockGuard { public: LockGuard(Mutex mutex) : _mutex(mutex) { _mutex.Lock(); } ~LockGuard() { _mutex.Unlock(); } private: Mutex _mutex; };使用LockGuard guard(mutex);创建对象时构造函数 ↓ Lock()对象离开作用域时析构函数 ↓ Unlock()这就是 RAII利用对象生命周期自动管理资源。这样即使return; break;提前离开作用域guard的析构函数仍然会自动调用从而释放 mutex。十三、C11中的标准互斥锁C11 已经提供了#include mutex std::mutex mutex;以及std::lock_guardstd::mutex例如std::mutex mutex; void func() { std::lock_guardstd::mutex guard(mutex); // 临界区 }执行过程创建 guard ↓ 自动 lock ↓ 执行临界区 ↓ 离开作用域 ↓ guard 析构 ↓ 自动 unlock互斥题分析第一题if中应该判断是否有锁即lockfalse第二题: 左边swap只有一条语句交换具有原子性右边有3条语句实现每一条语句在实现时都有可能被其他线程切换走不具备原子性所以不能

相关新闻

2026/9/1 4:46:00

掌阅大数据岗秋招笔试复盘:考点拆解与实战冲刺

掌阅集团大数据岗秋招笔试复盘:从考点拆解到实战冲刺每年秋招,内容平台类公司的大数据岗位都是兵家必争之地。掌阅作为国内头部的数字阅读平台,其大数据岗笔试既有互联网大厂的通用套路,又带着明显的内容业务特色。这篇文章基于我…

2026/9/1 4:46:00

opencode 我们为什么删除了看门狗机制

opencode 我们为什么删除了看门狗机制本文基于 opencode-goal 插件(开源,SDK 1.18)真实实现编写。主题:旧版独立 watchdog 定时器的删除理由,以及卡死救护如何内聚进 TICK 门链。1. 旧架构 旧版存在两套后台定时器: TICK(1 秒):驱动自动续推;独立 watchdog(按配置时限):自动中止…

2026/9/1 5:01:01

EPLAN在锂电池生产线电气设计中的实战应用与效率提升

这次我们来看一个 EPLAN 在锂电池生产线电气设计中的实际应用案例。对于从事非标自动化、产线集成或新能源设备设计的电气工程师来说,如何高效、规范地完成一套复杂生产线的电气设计,是一个核心挑战。EPLAN 作为专业的电气工程设计软件,其价值…

2026/9/1 5:01:01

新能源汽车电池缺陷检测数据集全解析:从标注规范到YOLOv8训练实践

简介:面向新能源汽车电池健康状态估计与剩余寿命预测研究,这份项目代码为智能汽车安全技术全国重点实验室发布的三元锂离子电池运行数据集提供了轻量级使用入口。原始数据采集自300辆真实运营车辆,覆盖0—50万公里里程、0.5—4年运行周期&…

2026/9/1 5:01:01

AI数据分析怎么做?一套SOP打通ChatBI、Data Agent与Skill

过去做一次数据分析,往往要经历提需求、找数据、写SQL、做图表、解释异常、整理报告等多个环节。现在有了大模型,不少企业希望直接接入ChatBI,让业务人员通过自然语言完成分析。但真正落地后会发现:能回答问题,不等于能…

2026/9/1 5:01:01

跌倒检测数据集处理全攻略:乱码修复、VOC转YOLO与模型训练

简介:一套面向跌倒检测算法研发与验证的图像数据集,适合计算机视觉、模式识别及机器学习方向的研究者与学生使用。数据包共收录2000个文件,其中560张jpg图像覆盖多视角、不同光照和室内外环境下的真实跌倒画面,1440个xml文件为每张…

2026/9/1 4:56:01

LangChain实战:从Chain到RAG与Agent的完整工程落地指南

简介:这是一份面向初学者的LangChain入门与实践项目代码包。LangChain是当前流行的基于Python的开源框架,通过模块化设计将大语言模型与外部数据、工具及记忆能力连接,能有效解决信息滞后、无法执行外部操作、记忆有限等痛点,并支…

2026/8/31 1:05:20

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/8/31 2:14:20

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/8/31 1:41:28

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/1 0:00:42

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/1 0:00:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/1 0:00:42

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

2026/9/1 0:00:42

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/1 0:00:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/1 0:00:42

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…