4-13.【OC】【锁】recursive mutex 内部需要维护哪些额外状态?

1 阅读2分钟

相比于普通的互斥锁(Mutex),递归锁(Recursive Mutex) 的核心区别在于它允许“锁的所有者”多次进入而不死锁。为了实现这一点,它必须在底层内存中维护普通锁所没有的额外状态信息

pthread_mutex 或 Swift 的底层实现中,递归锁主要增加并维护以下三个关键状态:


1. 持有者线程 ID (Owner Thread ID)

普通锁只知道“锁是否被占用”(通常是一个 0 或 1 的标记)。而递归锁必须明确记录**“当前是谁锁住了我”**。

  • 作用: 当一个线程尝试加锁时,系统会将当前线程的 Thread ID 与锁内部记录的 Owner ID 进行对比。

  • 逻辑: * 如果 Thread ID == Owner ID,说明是同一线程重入,直接允许通过。

    • 如果 Thread ID != Owner ID,则进入标准的竞争/挂起流程。

2. 递归计数器 (Recursion Count / Nesting Level)

这是递归锁能够“成对释放”的关键。它记录了当前线程进入了多少层临界区。

  • 累加: 每次同一线程成功调用 lock(),计数器 +1+1
  • 递减: 每次调用 unlock(),计数器 1-1
  • 真正释放: 只有当计数器归零(Count=0Count = 0)时,锁才会清除 Owner Thread ID,并将锁的状态彻底设为“未占用”,从而允许其他线程竞争。

3. 线程局部存储的影子备份 (Optional/Performance Optimization)

在一些高性能实现(如 Objective-C 的 @synchronized)中,为了避免频繁查询内核级别的 Thread ID(这通常涉及系统调用),系统会在 TLS (Thread Local Storage) 中备份一份当前线程持有的锁列表。

  • 作用: 线程先检查自己的私有缓存(TLS),如果发现目标锁已经在自己的“持有名单”里,直接在用户态完成计数加减,避开昂贵的内核操作。

4. 递归锁与普通锁的状态对比

状态位普通互斥锁 (Normal Mutex)递归锁 (Recursive Mutex)
Locked State简单布尔值 (0/1)计数器 (Int)
Owner ID通常不记录(或仅用于调试)强制记录 (Thread ID)
加锁逻辑发现已锁定则挂起发现已锁定但 Owner 是自己则继续
释放逻辑直接设为 0计数减至 0 时才设为 0

5. 潜在代价:死锁风险的变种

虽然递归锁解决了“自死锁”,但它引入了另一种风险。因为内部需要维护这些状态,它在性能上比普通锁稍慢。此外,如果你在临界区内忘记了配对调用 unlock(),锁将永远被该线程持有,导致其他所有尝试获取该锁的线程永久阻塞。

注意: 在一些复杂的 C++ 环境中,递归锁内部还会维护一个 “锁类型标记” (Type Flag),用来在运行时区分该锁当前是被配置为 PTHREAD_MUTEX_RECURSIVE 还是 PTHREAD_MUTEX_ERRORCHECK