NSCondition 封装了 POSIX 线程(pthread)中的两个核心原语:互斥锁(Mutex) 和 条件变量(Condition Variable) 。
它的模型本质上是一个 “生产者-消费者” 或 “等待-通知” 同步机制,解决了多线程协作中“只有当某个特定条件满足时才继续执行”的问题。
1. NSCondition 的结构模型
一个 NSCondition 对象内部同时包含了两个部分:
- 一个 Lock: 用于保护共享资源(即“条件”本身)。
- 一个 Wait-Set(等待集): 存放那些正在等待该条件被触发的线程。
2. 核心操作流程
NSCondition 的典型工作模型遵循以下三个阶段:
A. 等待者(Consumer)
-
加锁 (
lock): 保护条件检查的原子性。 -
循环检查条件 (
while (!condition)): 必须使用while而不是if,以防止“虚假唤醒”(Spurious Wakeup)。 -
等待 (
wait): 这是最神奇的一步。当执行wait时,NSCondition会自动做三件事:- 释放锁: 让其他线程(生产者)能进来修改条件。
- 阻塞: 将当前线程挂起并放入等待集。
- 重新加锁: 当被唤醒时,在
wait返回前,它会再次尝试获取锁。
-
执行任务并解锁 (
unlock)。
B. 通知者(Producer)
-
加锁 (
lock)。 -
修改状态: 改变那个被等待的共享变量。
-
发送信号 (
signal或broadcast):signal: 唤醒等待集中的一个线程。broadcast: 唤醒所有等待的线程。
-
解锁 (
unlock)。
3. 为什么需要“循环检查”?
在条件变量模型中,虚假唤醒是一个必须处理的现象:
- 操作系统原因: 在某些多处理器系统上,由于内核调度的实现,即使没有信号发出,
wait也可能返回。 - 竞争原因: 假设线程 A 被唤醒了,但在它重新拿到锁之前,线程 C 突然冲进来把条件又改回了“不满足”。
因此,代码必须长这样:
Swift
lock.lock()
while (dataArray.count == 0) { // 必须循环检查
lock.wait()
}
let item = dataArray.removeFirst()
lock.unlock()
4. NSCondition vs DispatchSemaphore
虽然两者都可以实现阻塞等待,但模型侧重不同:
| 特性 | NSCondition | DispatchSemaphore |
|---|---|---|
| 同步依据 | 逻辑状态(如:数组是否为空) | 计数值(信号量数量) |
| 状态维护 | 开发者自己维护状态变量 | 信号量自动维护原子计数 |
| 灵活性 | 高,可以表达复杂的逻辑组合 | 低,仅限于数字增减 |
| 性能 | 略低(涉及更多上下文切换) | 极高(用户态自旋优先) |
5. 实战场景:异步转同步
NSCondition 最常见的实战场景是:在主线程发起异步请求,并等待其结果返回后再继续。 虽然现在更多用 async/await,但在维护旧代码或底层框架时,它依然是处理“非计数型”阻塞的最佳工具。