5.0 KiB
5.0 KiB
很好,这一步其实就是把你刚刚理解的“信号量体系”再精细化一下。 如果说信号量是“通用同步工具”,那互斥量(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::mutex和std::atomic的关系
这些一旦理解,你对“并发”这一块基本就是工程级水平了。