Appearance
03 - Concurrency 深度
目录
1. 线程模型与调度策略
iOS 线程模型:
┌────────────────────────────────────────────────────────────────────────┐
│ │
│ 线程类型: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 主线程(Main Thread) │ │
│ │ • UI 更新唯一线程 │ │
│ │ • RunLoop 驱动事件循环 │ │
│ │ • 默认优先级(userInteractive) │ │
│ │ • 不能阻塞 │ │
│ │ • 所有 UI 操作必须在此线程 │ │
│ │ │ │
│ │ 后台线程(Background Thread) │ │
│ │ • 网络请求、数据处理 │ │
│ │ • 优先级可配置(QoS) │ │
│ │ • 数量受系统限制(约 64 个) │ │
│ │ • 通过 DispatchQueue 创建 │ │
│ │ │ │
│ │ 线程池(Thread Pool) │ │
│ │ • GCD 自动管理线程池 │ │
│ │ • 自动线程复用 │ │
│ │ • 线程数量根据负载动态调整 │ │
│ │ • 空闲线程会自动回收 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 调度策略: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 优先级抢占调度(Preemptive Scheduling) │ │
│ │ • 高优先级任务优先执行 │ │
│ │ • 低优先级任务可能被抢占 │ │
│ │ • iOS 系统使用 CFScheduler │ │
│ │ │ │
│ │ 时间片轮转调度(Round-Robin) │ │
│ │ • 每个线程分配固定时间片 │ │
│ │ • 时间片用完 → 切换到下一个线程 │ │
│ │ • 适用于同等优先级线程 │ │
│ │ │ │
│ │ 实时调度(Real-Time Scheduling) │ │
│ │ • 最高优先级 │ │
│ │ • 保证响应时间 │ │
│ │ • 仅用于系统关键任务 │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ QoS 映射: │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ UserInteractive → 最高优先级(保证 UI 流畅) │ │
│ │ UserInitiated → 高优先级(用户发起的任务) │ │
│ │ Default → 默认优先级(系统推荐) │ │
│ │ Utility → 低优先级(耗时任务) │ │
│ │ Background → 最低优先级(后台刷新等) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 线程性能分析: │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ • 线程创建:约 1MB 内存(栈空间) │ │
│ │ • 线程切换:约 10-50μs │ │
│ │ • GCD 线程池:复用现有线程(零创建开销) │ │
│ │ • async/await 协程:约 4KB 内存 │ │
│ │ • 大量线程 → 内存压力/上下文切换开销 │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────────┐
*/2. 死锁:产生条件与避免方案
死锁分析:
┌──────────────────────────────────────────────────────────────┐
│ │
│ 死锁的四个必要条件(Coffman Conditions): │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 1. 互斥(Mutual Exclusion)— 资源只能被一个线程占用 │ │
│ │ 2. 持有并等待(Hold and Wait)— 持有资源时等待其他资源 │ │
│ │ 3. 不可抢占(No Preemption)— 资源不能被强制回收 │ │
│ │ 4. 循环等待(Circular Wait)— 线程形成等待环 │ │
│ │ │ │
│ │ 死锁检测: │ │
│ │ • 资源分配图(Resource Allocation Graph) │ │
│ │ • 如果图中存在环 → 可能死锁 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 常见的 iOS 死锁场景: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 场景 1:sync 到当前队列 │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ 主线程: │ │ │
│ │ │ 1. 执行 sync 到主队列 │ │ │
│ │ │ 2. 等待主队列任务完成 │ │ │
│ │ │ 3. 主队列任务需要主线程执行 │ │ │
│ │ │ 4. 死锁! │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │
│ │ 场景 2:线程 A 锁 1 → 锁 2 / 线程 B 锁 2 → 锁 1 │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Thread A: lock(1) → lock(2) │ │ │
│ │ │ Thread B: lock(2) → lock(1) │ │ │
│ │ │ → 互相等待 → 死锁 │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │
│ │ 场景 3:DispatchGroup 等待超时 │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ group.wait(timeout: .now() + 5) │ │ │
│ │ │ → 如果任务未 enter/leave 平衡 │ │ │
│ │ │ → 永久等待 → 逻辑死锁 │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │
│ │ 场景 4:主线程同步调用异步任务 │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ 主线程 sync 到主队列 │ │ │
│ │ │ 或 async 到主队列 同步等待 │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 避免死锁的策略: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 1. 始终使用 async 而非 sync │ │
│ │ 2. 统一锁的顺序(所有线程按相同顺序获取锁) │ │
│ │ 3. 使用 DispatchQueue 代替原生锁 │ │
│ │ 4. 使用 DispatchGroup 确保 enter/leave 平衡 │ │
│ │ 5. 设置超时(group.wait(timeout:)) │ │
│ │ 6. 使用 actor 隔离(避免手动锁) │ │
│ │ 7. 使用 os_unfair_lock(可尝试获取,不会阻塞等待) │ │
│ │ 8. 避免嵌套锁 │ │
│ │ 9. 使用 dispatch_semaphore 而非 manual lock │ │
│ │ 10. 使用 Thread Sanitizer 检测死锁 │ │
│ │ │ │
│ │ 死锁检测工具: │ │
│ │ • Thread Sanitizer(TSan) │ │
│ │ • Instruments → Thread Debugger │ │
│ │ • os_log 日志分析 │ │
│ │ • Xcode debugger │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ 最佳实践: │
│ • 永远不要在主线程同步调用主队列任务 │
│ • 使用 async/await 替代 sync │
│ • 使用 actor 代替手动锁 │
│ • 使用 DispatchGroup + timeout 防止永久等待 │
│ • 所有锁必须成对获取/释放(使用 defer) │
│ │
└──────────────────────────────────────────────────────────┘
*/3. 内存屏障与原子操作
内存屏障与原子操作:
┌─────────────────────────────────────────────────────────┐
│ │
│ 内存屏障(Memory Barrier): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ • 保证内存操作的顺序性 │ │
│ │ • 防止编译器/CPU 重排序 │ │
│ │ • 在多核 CPU 中至关重要 │ │
│ │ • iOS 中使用 os_atomic_* API │ │
│ │ │ │
│ │ 内存屏障类型: │ │
│ │ • acquire(获取)— 屏障后的读不能重排序到屏障前 │ │
│ │ • release(释放)— 屏障前的写不能重排序到屏障后 │ │
│ │ • acquire-release — 组合屏障 │ │
│ │ • seq_cst(顺序一致性)— 最强的内存屏障 │ │
│ │ │ │
│ │ 使用场景: │ │
│ │ • 多线程共享变量的读写 │ │
│ │ • 实现无锁数据结构 │ │
│ │ • 实现同步原语(如 spin lock) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 原子操作(Atomic Operations): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ • 不可分割的操作(要么全部执行,要么不执行) │ │
│ │ • 不需要锁 │ │
│ │ • 比锁更高效 │ │
│ │ • iOS 使用 os_unfair_lock 和 os_atomic_* API │ │
│ │ │ │
│ │ os_unfair_lock(推荐 ✅) │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ let lock = os_unfair_lock() │ │ │
│ │ │ os_unfair_lock_lock(&lock) │ │ │
│ │ │ // 临界区 │ │ │
│ │ │ os_unfair_lock_unlock(&lock) │ │ │
│ │ │ │ │ │
│ │ │ // 与 NSLock 对比 │ │ │
│ │ │ • os_unfair_lock:C API,更高效 │ │ │
│ │ │ • NSLock:ObjC 封装,更易用 │ │ │
│ │ │ • 性能:os_unfair_lock > NSLock │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ os_atomic(原子操作 API) │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ os_atomic_increment(&counter, memory_order_relaxed) │
│ │ │ os_atomic_decrement(&counter, memory_order_relaxed) │
│ │ │ os_atomic_load(&var, memory_order_relaxed) │ │ │
│ │ │ os_atomic_store(&var, newValue, memory_order_relaxed) │
│ │ │ │ │ │
│ │ │ memory_order 类型: │ │ │
│ │ │ • relaxed:无顺序保证(最高性能) │ │ │
│ │ │ • acquire:获取语义 │ │ │
│ │ │ • release:释放语义 │ │ │
│ │ │ • acquire-release:组合 │ │ │
│ │ │ • seq_cst:顺序一致(最强) │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 无锁编程(Lock-Free Programming): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 优点: │ │
│ │ • 无锁竞争 → 更高性能 │ │
│ │ • 不会死锁 │ │
│ │ • 线程安全 │ │
│ │ │ │
│ │ 缺点: │ │
│ │ • 实现复杂 │ │
│ │ • 容易引入微妙 bug │ │
│ │ • 不适合所有场景 │ │
│ │ │ │
│ │ CAS(Compare-And-Swap)原子操作: │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ 1. 读取旧值 │ │ │
│ │ │ 2. 比较旧值与期望值 │ │ │
│ │ │ 3. 相等 → 更新为新值 │ │ │
│ │ │ 4. 不等 → 重试 │ │ │
│ │ │ │ │ │
│ │ │ 应用场景: │ │ │
│ │ │ • 无锁队列 │ │ │
│ │ │ • 引用计数 │ │ │
│ │ │ • 计数器 │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 性能分析: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ • NSLock:约 100-200ns │ │
│ │ • os_unfair_lock:约 50-100ns │ │
│ │ • os_atomic:约 10-50ns │ │
│ │ • CAS:约 10-30ns │ │
│ │ • 锁 vs 原子操作:原子操作更快(无内核调用) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
*/4. 并发设计模式
并发设计模式:
┌─────────────────────────────────────────────────────────┐
│ │
│ 1. 生产者-消费者模式 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ actor Queue { │ │
│ │ private var items: [Data] = [] │ │
│ │ private let maxItems = 100 │ │
│ │ │ │
│ │ func enqueue(item: Data) async { │ │
│ │ while items.count >= maxItems { │ │
│ │ await Task.yield() // 等待空间 │ │
│ │ } │ │
│ │ items.append(item) │ │
│ │ } │ │
│ │ │ │
│ │ func dequeue() async -> Data? { │ │
│ │ guard !items.isEmpty else { │ │
│ │ await Task.yield() // 等待数据 │ │
│ │ return items.isEmpty ? nil : dequeue() │ │
│ │ } │ │
│ │ return items.removeFirst() │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ 使用: │ │
│ │ Task { for item in source { await queue.enqueue(item: item) } } │
│ │ Task { while let data = await queue.dequeue() { process(data) } } │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 2. 工作线程池模式 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ actor ThreadPool { │ │
│ │ private var workers: [Int: Task<Void, Never>] = [] │ │
│ │ private var taskQueue: [() async -> Void] = [] │ │
│ │ private let maxWorkers = 8 │ │
│ │ │ │
│ │ func submit(work: @escaping () async -> Void) { │ │
│ │ if workers.count < maxWorkers { │ │
│ │ let worker = Task { │ │
│ │ while let task = taskQueue.first { │ │
│ │ taskQueue.removeFirst() │ │
│ │ await task() │ │
│ │ } │ │
│ │ } │ │
│ │ workers[worker.hashValue] = worker │ │
│ │ } else { │ │
│ │ taskQueue.append(work) │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 3. Future/Promise 模式 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ class Promise<T> { │ │
│ │ private var continuation: CheckedContinuation<T, Error>? │
│ │ private var value: Result<T, Error>? │ │
│ │ │ │
│ │ func then(_ handler: @escaping (T) async throws -> R) -> Promise<R> { │
│ │ return Promise<R> { resolver in │ │
│ │ let result = try await value.get() │ │
│ │ let newValue = try await handler(result) │ │
│ │ resolver.fulfill(newValue) │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 4. Barrier(屏障)模式 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ actor SharedData { │ │
│ │ private var data: [String: Data] = [:] │ │
│ │ private var isReady = false │ │
│ │ │ │
│ │ func write(key: String, value: Data) async { │ │
│ │ data[key] = value │ │
│ │ if data.count >= threshold { isReady = true } │ │
│ │ } │ │
│ │ │ │
│ │ func waitForAll() async -> [String: Data]? { │ │
│ │ guard isReady else { │ │
│ │ await Task.yield() │ │
│ │ return data.count >= threshold ? data : nil │ │
│ │ } │ │
│ │ return data │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 5. Observer(观察者)模式 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ actor EventEmitter { │ │
│ │ private var listeners: [(Event) -> Void] = [] │ │
│ │ │ │
│ │ func subscribe(listener: @escaping (Event) -> Void) { │
│ │ listeners.append(listener) │ │
│ │ } │ │
│ │ │ │
│ │ func publish(event: Event) async { │ │
│ │ for listener in listeners { │ │
│ │ await listener(event) │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 模式选择指南: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 场景 │ 推荐模式 │ │
│ │ ──── │ ────── │ │
│ │ 大量并发数据流 │ 生产者-消费者 │ │
│ │ 固定数量线程池 │ 工作线程池 │ │
│ │ 异步操作链 │ Future/Promise │ │
│ │ 等待多个任务完成 │ Barrier │ │
│ │ 事件通知 │ Observer │ │
│ │ 共享状态 │ Actor │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
*/5. 面试题汇总
高频面试题
Q1: 死锁的四个必要条件?
答:互斥、持有并等待、不可抢占、循环等待。避免任一条件即可打破死锁。
Q2: iOS 线程模型?
答:主线程(UI)、后台线程(网络/数据处理)、线程池(GCD 自动管理)。QoS 决定优先级。
Q3: 内存屏障的作用?
答:保证内存操作的顺序性,防止重排序。acquire/release/seq_cst 四种语义。
Q4: 原子操作 vs 锁?
答:原子操作更快(无内核调用),不需要锁。OS_unfair_lock > NSLock > 原子操作(CAS)。
Q5: 并发设计模式?
答:生产者-消费者、工作线程池、Future/Promise、Barrier、Observer。根据场景选择。