4-14.【OC】【锁】NSCondition 的条件变量模型是怎样的?

4 阅读2分钟

NSCondition 封装了 POSIX 线程(pthread)中的两个核心原语:互斥锁(Mutex)条件变量(Condition Variable)

它的模型本质上是一个 “生产者-消费者”“等待-通知” 同步机制,解决了多线程协作中“只有当某个特定条件满足时才继续执行”的问题。


1. NSCondition 的结构模型

一个 NSCondition 对象内部同时包含了两个部分:

  1. 一个 Lock: 用于保护共享资源(即“条件”本身)。
  2. 一个 Wait-Set(等待集): 存放那些正在等待该条件被触发的线程。

2. 核心操作流程

NSCondition 的典型工作模型遵循以下三个阶段:

A. 等待者(Consumer)

  1. 加锁 (lock): 保护条件检查的原子性。

  2. 循环检查条件 (while (!condition)): 必须使用 while 而不是 if,以防止“虚假唤醒”(Spurious Wakeup)。

  3. 等待 (wait): 这是最神奇的一步。当执行 wait 时,NSCondition 会自动做三件事:

    • 释放锁: 让其他线程(生产者)能进来修改条件。
    • 阻塞: 将当前线程挂起并放入等待集。
    • 重新加锁: 当被唤醒时,在 wait 返回前,它会再次尝试获取锁。
  4. 执行任务并解锁 (unlock)。

B. 通知者(Producer)

  1. 加锁 (lock)。

  2. 修改状态: 改变那个被等待的共享变量。

  3. 发送信号 (signalbroadcast):

    • signal: 唤醒等待集中的一个线程。
    • broadcast: 唤醒所有等待的线程。
  4. 解锁 (unlock)。


3. 为什么需要“循环检查”?

在条件变量模型中,虚假唤醒是一个必须处理的现象:

  • 操作系统原因: 在某些多处理器系统上,由于内核调度的实现,即使没有信号发出,wait 也可能返回。
  • 竞争原因: 假设线程 A 被唤醒了,但在它重新拿到锁之前,线程 C 突然冲进来把条件又改回了“不满足”。

因此,代码必须长这样:

Swift

lock.lock()
while (dataArray.count == 0) { // 必须循环检查
    lock.wait()
}
let item = dataArray.removeFirst()
lock.unlock()

4. NSCondition vs DispatchSemaphore

虽然两者都可以实现阻塞等待,但模型侧重不同:

特性NSConditionDispatchSemaphore
同步依据逻辑状态(如:数组是否为空)计数值(信号量数量)
状态维护开发者自己维护状态变量信号量自动维护原子计数
灵活性高,可以表达复杂的逻辑组合低,仅限于数字增减
性能略低(涉及更多上下文切换)极高(用户态自旋优先)

5. 实战场景:异步转同步

NSCondition 最常见的实战场景是:在主线程发起异步请求,并等待其结果返回后再继续。 虽然现在更多用 async/await,但在维护旧代码或底层框架时,它依然是处理“非计数型”阻塞的最佳工具。