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

378 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
很好,这一步其实就是把你刚刚理解的“信号量体系”再精细化一下。
如果说信号量是“通用同步工具”那互斥量Mutex就是一个**专门为“互斥”优化的特化版本**。
我按你现在的理解层级来讲:从本质 → 和信号量的关系 → 实现 → 设计差异。
---
# 一、互斥量是什么(本质)
互斥量Mutex, mutual exclusion本质上是
> **一个带“所有权”的二值锁**
状态只有两种:
```text
locked (已上锁)
unlocked (未上锁)
```
但关键点在这里:
> **谁加锁,必须由谁解锁**
这点和信号量是本质区别。
---
# 二、最直观理解
你可以这样对比:
## 信号量Semaphore
```text
像“公共许可证”
谁拿都行,谁还也行
```
---
## 互斥量Mutex
```text
像“私人钥匙”
谁锁门,必须谁开门
```
---
# 三、基本操作
互斥量通常只有两个操作:
```c
lock(mutex) // 加锁
unlock(mutex) // 解锁
```
语义:
---
### lock
```c
if (mutex )
线 owner
```
---
### unlock
```c
线 != owner
线
```
---
# 四、和信号量的关系(非常重要)
你可以认为:
> **Mutex ≈ Binary Semaphore + Ownership**
但这个“≈”是有坑的。
---
## 相同点
* 都可以实现互斥
* 都会阻塞线程
* 底层都依赖原子操作
---
## 不同点(核心)
| 特性 | Semaphore | Mutex |
| ---- | --------- | ------ |
| 计数 | 可以 >1 | 只能 0/1 |
| 所有权 | ❌ 没有 | ✅ 有 |
| 误用检测 | ❌ 没有 | ✅ 可以检测 |
| 用途 | 同步 + 互斥 | 纯互斥 |
---
### 一个关键例子
信号量允许:
```c
// 线程A
P(S)
// 线程B
V(S)
```
合法 ✅
---
但 mutex 不允许:
```c
线A: lock(m)
线B: unlock(m) // ❌ 错误
```
---
# 五、为什么需要 Mutex而不是只用信号量
这是设计哲学问题,很关键。
---
## 1. 防止“逻辑错误”
信号量太自由:
```c
P(S)
// 忘了 V(S)
```
或者:
```c
V
```
系统不会报错,但逻辑已经炸了。
---
Mutex
```c
unlock owner
```
→ 可以直接检测错误
---
## 2. 表达语义更清晰
```c
mutex.lock()
// 临界区
mutex.unlock()
```
比:
```c
P(S)
// ...
V(S)
```
更直观:
> “我就是要保护这段代码”
---
## 3. 性能优化空间更大
Mutex 可以做很多优化(后面讲)
---
# 六、实现原理(比信号量更复杂一点)
你现在重点要理解两个阶段:
---
## 阶段1用户态快速路径fast path
现代 mutex比如 Linux pthread_mutex
```c
if (CAS(&lock, 0, 1))
```
---
## 阶段2失败 → 进入内核slow path
```c
```
---
# 七、关键优化futex你这个阶段非常值得了解
在 Linux 中:
> **mutex ≈ futex + 用户态CAS**
futex = fast userspace mutex
核心思想:
```text
能不进内核就不进内核
```
---
流程:
```text
加锁:
CAS 成功 → 完成(用户态)
CAS 失败 → futex_wait进入内核
解锁:
CAS 释放
如果有人等待 → futex_wake
```
---
# 八、Mutex 的几种类型(高级但重要)
---
## 1. 普通 mutex
最基础的锁
---
## 2. 可重入锁recursive mutex
允许:
```c
lock()
lock()
unlock()
unlock()
```
同一线程多次加锁
---
## 3. 自旋锁spinlock
```text
不阻塞,一直忙等
```
适合:
* 临界区极短
* 内核中断上下文
---
## 4. 读写锁rwlock
```text
多个读可以并发
写必须独占
```
---
# 九、经典问题:优先级反转
这个是 mutex 才有的典型问题:
```text
低优先级线程持有锁
高优先级线程在等
中优先级线程一直抢CPU
```
→ 高优先级线程反而跑不了
---
解决:
> **优先级继承priority inheritance**
---
# 十、总结(给你一个结构化认知)
你现在可以这样构建体系:
---
## 同步工具分层
```text
硬件层:
原子指令CAS
内核层:
futex / 调度器
抽象层:
semaphore通用
mutex互斥专用
```
---
## 核心区别一句话
> **Semaphore 是“资源计数器”Mutex 是“带所有权的锁”**
---
# 十一、一个建议(非常实用)
你以后写代码:
* 需要“限制资源数量” → 用 semaphore
* 需要“保护临界区” → 用 mutex
---
如果你想继续深入,我可以带你把这几个东西彻底打通(强烈建议):
* pthread_mutex 的源码级实现
* futex 的 syscall 细节
* 自旋锁 vs 阻塞锁的性能边界
* C++ `std::mutex``std::atomic` 的关系
这些一旦理解,你对“并发”这一块基本就是工程级水平了。
---