Files
Operating-System/Thread/mutex.md
2026-03-22 17:40:44 +08:00

5.0 KiB
Raw Permalink Blame History

很好,这一步其实就是把你刚刚理解的“信号量体系”再精细化一下。 如果说信号量是“通用同步工具”那互斥量Mutex就是一个专门为“互斥”优化的特化版本

我按你现在的理解层级来讲:从本质 → 和信号量的关系 → 实现 → 设计差异。


一、互斥量是什么(本质)

互斥量Mutex, mutual exclusion本质上是

一个带“所有权”的二值锁

状态只有两种:

locked   (已上锁)
unlocked (未上锁)

但关键点在这里:

谁加锁,必须由谁解锁

这点和信号量是本质区别。


二、最直观理解

你可以这样对比:

信号量Semaphore

像“公共许可证”
谁拿都行,谁还也行

互斥量Mutex

像“私人钥匙”
谁锁门,必须谁开门

三、基本操作

互斥量通常只有两个操作:

lock(mutex)     // 加锁
unlock(mutex)   // 解锁

语义:


lock

if (mutex 已被占用)
    阻塞
否则
    获取锁(标记为已占用)
    记录当前线程为 owner

unlock

如果 当前线程 != owner
    报错(或未定义行为)

释放锁
唤醒一个等待线程

四、和信号量的关系(非常重要)

你可以认为:

Mutex ≈ Binary Semaphore + Ownership

但这个“≈”是有坑的。


相同点

  • 都可以实现互斥
  • 都会阻塞线程
  • 底层都依赖原子操作

不同点(核心)

特性 Semaphore Mutex
计数 可以 >1 只能 0/1
所有权 没有
误用检测 没有 可以检测
用途 同步 + 互斥 纯互斥

一个关键例子

信号量允许:

// 线程A
P(S)

// 线程B
V(S)

合法


但 mutex 不允许:

线程A: lock(m)
线程B: unlock(m)  // ❌ 错误

五、为什么需要 Mutex而不是只用信号量

这是设计哲学问题,很关键。


1. 防止“逻辑错误”

信号量太自由:

P(S)
// 忘了 V(S)

或者:

 V 了一次

系统不会报错,但逻辑已经炸了。


Mutex

unlock  owner

→ 可以直接检测错误


2. 表达语义更清晰

mutex.lock()
// 临界区
mutex.unlock()

比:

P(S)
// ...
V(S)

更直观:

“我就是要保护这段代码”


3. 性能优化空间更大

Mutex 可以做很多优化(后面讲)


六、实现原理(比信号量更复杂一点)

你现在重点要理解两个阶段:


阶段1用户态快速路径fast path

现代 mutex比如 Linux pthread_mutex

if (CAS(&lock, 0, 1))
    成功获取锁(不进内核)

阶段2失败 → 进入内核slow path

获取失败:
    进入内核
    加入等待队列
    阻塞

七、关键优化futex你这个阶段非常值得了解

在 Linux 中:

mutex ≈ futex + 用户态CAS

futex = fast userspace mutex

核心思想:

能不进内核就不进内核

流程:

加锁:
    CAS 成功 → 完成(用户态)
    CAS 失败 → futex_wait进入内核

解锁:
    CAS 释放
    如果有人等待 → futex_wake

八、Mutex 的几种类型(高级但重要)


1. 普通 mutex

最基础的锁


2. 可重入锁recursive mutex

允许:

lock()
lock()
unlock()
unlock()

同一线程多次加锁


3. 自旋锁spinlock

不阻塞,一直忙等

适合:

  • 临界区极短
  • 内核中断上下文

4. 读写锁rwlock

多个读可以并发
写必须独占

九、经典问题:优先级反转

这个是 mutex 才有的典型问题:

低优先级线程持有锁
高优先级线程在等
中优先级线程一直抢CPU

→ 高优先级线程反而跑不了


解决:

优先级继承priority inheritance


十、总结(给你一个结构化认知)

你现在可以这样构建体系:


同步工具分层

硬件层:
    原子指令CAS

内核层:
    futex / 调度器

抽象层:
    semaphore通用
    mutex互斥专用

核心区别一句话

Semaphore 是“资源计数器”Mutex 是“带所有权的锁”


十一、一个建议(非常实用)

你以后写代码:

  • 需要“限制资源数量” → 用 semaphore
  • 需要“保护临界区” → 用 mutex

如果你想继续深入,我可以带你把这几个东西彻底打通(强烈建议):

  • pthread_mutex 的源码级实现
  • futex 的 syscall 细节
  • 自旋锁 vs 阻塞锁的性能边界
  • C++ std::mutexstd::atomic 的关系

这些一旦理解,你对“并发”这一块基本就是工程级水平了。