iOS “资源竞争”加锁使用优化

0 阅读6分钟

在 iOS 开发中,处理 “资源竞争”(多线程并发访问共享资源)时,加锁是保证线程安全的核心手段,但不当的锁使用会导致性能损耗、死锁等问题。

iOS 资源竞争的加锁优化核心是:“合适的锁 + 最小化范围 + 避免死锁 + 无锁优先”。

一、各种锁的性能对比(版本 & 特性)

锁类型iOS 版本性能场景是否推荐
@synchronizediOS 2.0+OC语法糖,是递归锁⚠️ 谨慎,性能差
NSRecursiveLockiOS 2.0+递归调用场景⚠️ 谨慎,性能差
pthread_mutexiOS 2.0+中等兼容性最强,跨平台✅ 老代码/底层封装
NSLockiOS 2.0+中等面向对象简单使用✅ 小项目常用
dispatch_semaphoreiOS 4.0+中等GCD 任务同步✅ 合理使用
os_unfair_lockiOS 10.0+高性能场景✅ 推荐 (C API)
Swift 5.5 ActoriOS 15.0+基于隔离机制的 “无锁”✅ 强烈推荐
OSAllocatedUnfairLockiOS 16.0+Swift 项目安全锁✅ 强烈推荐
Swift 6 MutexiOS 18.0+现代 Swift,未来趋势✅ 新项目/新系统

二、正确的使用建议(按 iOS 版本分层)

  1. iOS 10 以下

    • 推荐:pthread_mutex 或 NSLock
    • 说明:兼容性优先,性能一般。
  2. iOS 10 ~ 15

    • 推荐:os_unfair_lock(性能最优)。
    • Swift 项目中用时要小心释放问题(结构体值类型)。
  3. iOS 16 ~ 17

    • 推荐:OSAllocatedUnfairLockActor
    • 说明:Apple 推荐在 Swift 中用,系统封装,简易好用。
  4. iOS 18+

    • 推荐:Swift 6 Mutex
    • 说明:语法现代、安全、易用,属于未来趋势。

三、加锁原则

最小化锁的持有范围(减少阻塞)

锁的性能损耗主要来自 “线程等待”,缩短锁的持有时间是关键优化手段。

避免死锁(规范锁的使用顺序)

死锁常因 “循环等待锁” 导致,例如线程 A 持有锁 1 等待锁 2,线程 B 持有锁 2 等待锁 1。

避免嵌套锁

尽量用单锁解决问题,嵌套锁会增加死锁风险(除非用递归锁,但递归锁开销更高)。

用 “无锁” 机制替代显式加锁

原子操作(针对简单值)

对 IntBool 等简单类型,用 std::atomic(C++)或 Swift 原子属性(iOS 17+),比锁更高效。

Swift Actor(推荐)

Actor 是 Swift 5.5 引入的并发模型,通过 “隔离域” 保证状态只能被自身线程访问,外部通过 await 异步调用,避免显式加锁

四、根据系统版本,封装自适应最优锁实现

  • iOS 18+:用 Swift 6 的 Mutex(Synchronization 框架)。
  • iOS 16 ~ 17:用 OSAllocatedUnfairLock
  • iOS 10 ~ 15:用 os_unfair_lock
  • iOS 9 及以下:用 pthread_mutex

import Foundation
import os

#if canImport(Synchronization)
import Synchronization
#endif

/// 跨版本最优锁实现 iOS10+(支持递归锁)
/// - 非递归场景:自动选择性能最优锁(Swift Mutex > OSAllocatedUnfairLock > os_unfair_lock)
/// - 递归场景:使用 NSRecursiveLock(Foundation 原生,更安全)
/// - 推荐通过 `withLock` 方法使用,自动管理锁生命周期
final class OptimalLock {
    private let impl: Locking
    
    /// 初始化锁
    /// - Parameter recursive: 是否需要递归锁(同一线程可重复加锁)
    init(recursive: Bool = false) {
        if recursive {
            // 递归场景
            impl = RecursiveLockWrapper()
        } else {
            // 非递归场景:按系统版本选择高性能锁
            if #available(iOS 18, *) {
                impl = MutexLock()
            } else if #available(iOS 16, *) {
                impl = OSAllocatedUnfairLockWrapper()
            } else {
                impl = UnfairLockWrapper()
            }
        }
    }
    
    /// 执行临界区代码(自动加锁/解锁)
    /// 在抛出错误前会统一打印错误信息
    @discardableResult
    func withLock<T>(_ block: () throws -> T) rethrows -> T {
        do {
            return try impl.withLock(block)
        } catch {
            // 统一打印错误信息
            DebugToast.showFailed(text:"OptimalLock 临界区执行错误: (error)")
            // 打印错误堆栈信息(可选,根据需要开启)
            debugPrint("错误堆栈: (Thread.callStackSymbols)")
            // 重新抛出错误,让调用者可以继续处理
            throw error
        }
    }
}

// MARK: - 核心协议定义
private protocol Locking {
    func withLock<T>(_ block: () throws -> T) rethrows -> T
}

// MARK: - 非递归锁实现(性能优先)
@available(iOS 18, *)
private final class MutexLock: Locking {
    private let mutex = Mutex(())
    func withLock<T>(_ block: () throws -> T) rethrows -> T {
        try mutex.withLock { _ in try block() }
    }
}

@available(iOS 16, *)
private final class OSAllocatedUnfairLockWrapper: Locking {
    private let lock = OSAllocatedUnfairLock()
    func withLock<T>(_ block: () throws -> T) rethrows -> T {
        lock.lock()
        defer { lock.unlock() }
        return try block()
    }
}

@available(iOS 10, *)
private final class UnfairLockWrapper: Locking {
    private var lock = os_unfair_lock()
    func withLock<T>(_ block: () throws -> T) rethrows -> T {
        os_unfair_lock_lock(&lock)
        defer { os_unfair_lock_unlock(&lock) }
        return try block()
    }
}

// MARK: - 递归锁实现(安全优先)
/// 封装 NSRecursiveLock(Foundation 原生递归锁,推荐用于递归场景)
@available(iOS 2, *)
private final class RecursiveLockWrapper: Locking {
    private let lock = NSRecursiveLock()
    
    func withLock<T>(_ block: () throws -> T) rethrows -> T {
        lock.lock()
        defer { lock.unlock() }
        return try block()
    }
}

extension OptimalLock {
    /// 带超时的临界区执行(仅递归锁有效),应对可能的死锁场景
    func withLock<T>(timeout: TimeInterval, _ block: () throws -> T) rethrows -> T? {
        guard let recursiveLock = impl as? RecursiveLockWrapper else {
            DebugToast.showFailed(text: "withLock(timeout:) 仅支持递归锁,请使用 OptimalLock(recursive: true) 初始化")
            return nil
        }
        return try recursiveLock.withLock(timeout: timeout, block)
    }
}

private extension RecursiveLockWrapper {
    func withLock<T>(timeout: TimeInterval, _ block: () throws -> T) rethrows -> T? {
        let deadline = Date().addingTimeInterval(timeout)
        guard lock.lock(before: deadline) else {
            DebugToast.showFailed(text: "递归锁调用超时")
            return nil
        }
        defer { lock.unlock() }
        return try block()
    }
}
let lock = OptimalLock() 
var shared = 0

DispatchQueue.concurrentPerform(iterations: 100) { _ in
    lock.withLock {
        shared += 1
    }
}

print("结果: (shared)") // 结果: 100

封装特点:

  1. 统一 API:对外始终是withLock { }
  2. 最优选择:不同 iOS 版本自动切换最佳锁实现。
  3. 错误处理:极端情况,若加锁失败会抛出错误,进行统一提示处理。

五、工具辅助:检测和优化锁性能

Thread Sanitizer(TSAN)

Xcode 内置工具,可检测代码中的 “数据竞争”(未加锁的共享资源访问),在 Debug 模式下启用:
Product > Scheme > Edit Scheme > Run > Diagnostics > Thread Sanitizer

Instruments(Time Profiler + Locks)用 “Locks” 模板分析锁的争用情况

  • 查看 os_unfair_lock_lock 等函数的耗时,定位频繁阻塞的锁。
  • 统计锁的等待次数,优化高频争用的锁(如替换为更轻量的同步方式)。

性能测试(Benchmark)

对不同锁的性能进行量化对比(如用 XCTest 测量每秒操作次数),选择最适合当前场景的锁。

六、iOS 锁机制的演进历史

pthread_mutex (iOS 2.0+)

  • 历史:POSIX 标准,C 层提供,最早就有。
  • 特性:通用、跨平台,支持递归锁/条件变量等。
  • 缺点:用法偏底层,封装成本高,API 不够 Swift 风格。

NSLock / NSRecursiveLock (iOS 2.0+)

  • 历史:Foundation 封装的锁。
  • 特性:面向对象,API 简单,支持递归(NSRecursiveLock)。
  • 缺点:性能一般,比 GCD 或 unfair lock 慢。

dispatch_semaphore / DispatchQueue (iOS 4.0+)

  • 历史:随 GCD 引入。
  • 特性:信号量值设为 1 时可充当互斥锁;串行队列也能避免资源竞争。
  • 缺点:语义上不直观;信号量误用容易死锁。

os_unfair_lock (iOS 10.0+)

  • 历史:Apple 官方替代 OSSpinLock
  • 特性:低开销锁,避免了优先级反转;性能最佳。
  • 缺点:是 C struct,值类型,Swift 使用要小心(释放时可能 UB)。

OSAllocatedUnfairLock (iOS 16.0+)

  • 历史:Swift 时代的封装,解决 os_unfair_lock 的不安全问题。
  • 特性:安全封装成类,内存管理正确;推荐在 Swift 中用。
  • 缺点:iOS 16+ 才可用,低版本无法兼容。

Swift 6 Mutex (iOS 18.0+)

  • 历史:Swift 6 引入 Synchronization 框架。
  • 特性:官方 Swift 原生 API,易用、安全、现代化;支持 lock { … } 作用域语法。
  • 缺点:⚠️ iOS 18 才能用,对兼容性要求高。