4-11.【OC】【锁】NSLock 与 pthread_mutex 在实现上有什么差异?

3 阅读3分钟

从层级结构上来说,NSLock 是对 pthread_mutex面向对象封装。虽然它们最终都指向底层的内核同步原语,但在性能、功能和使用场景上有着显著的差异。


1. 抽象层级的差异

  • pthread_mutex (C 语言层): 它是 POSIX 线程标准的一部分,直接操作内存结构。它非常轻量,没有类对象的开销,由内核和 pthread 库共同管理。
  • NSLock (Foundation 层): 它是一个 Objective-C 类,遵循 NSLocking 协议。当你创建一个 NSLock 时,你实际上是在堆上分配了一个对象,这包含了内存管理、虚函数表查找等 Objective-C 运行时的额外开销。

2. 实现细节对比

A. 内部构造

在 Objective-C 源码中,NSLock 内部封装了一个 pthread_mutex_t 以及一个数据结构。

B. 锁类型的默认值

  • pthread_mutex: 默认是 非递归锁 (PTHREAD_MUTEX_NORMAL) 。如果同一个线程尝试获取两次,会直接死锁。
  • NSLock: 同样是 非递归锁。如果你需要递归功能,必须使用 NSRecursiveLock(它内部封装的是 PTHREAD_MUTEX_RECURSIVE 类型的 pthread 锁)。

C. 错误处理与安全性

  • pthread_mutex: 通过返回值(如 0 表示成功,错误码表示失败)来通知结果。开发者必须手动检查返回值。
  • NSLock: 提供了更高级的 API,例如 lockBeforeDate:。这个方法允许线程尝试加锁并在超时后自动放弃,这在 pthread_mutex 中需要更复杂的 pthread_cond_timedwait 逻辑来实现。

3. 性能测试 (Performance)

由于 NSLock 是对象封装,其性能总是略逊于直接使用 pthread_mutex

锁类型调用开销 (约等)原因
pthread_mutex较低纯 C 调用,指令路径短。
NSLock较高涉及 objc_msgSend 消息转发、方法寻找、以及对象本身的内存开销。

实测数据: 在高频竞争环境下,pthread_mutex 的速度通常比 NSLock2 倍左右。虽然这个量级在普通业务中可以忽略不计,但在底层高性能库中至关重要。


4. Swift 时代的变革

在 Swift 中,NSLock 的使用频率正在下降,原因如下:

  1. Swift 并不原生支持 Objective-C 异常: NSLock 无法像 @synchronized 那样自动处理异常安全。
  2. 指针开销: Swift 访问 pthread_mutex 需要处理 UnsafeMutablePointer,虽然繁琐但性能极高。
  3. 替代者的崛起: os_unfair_lock 已经取代了这两者,成为 iOS 开发中高性能锁的首选。它在用户态自旋,仅在必要时挂起线程,性能远超基于内核等待的 pthread_mutex

5. 什么时候该选哪一个?

  • 使用 NSLock

    • 在编写通用的 Objective-C / Swift 业务逻辑时,追求代码可读性。
    • 需要利用 lockBeforeDate: 等超时特性。
  • 使用 pthread_mutex

    • 在编写跨平台的 C/C++ 核心组件时。
    • 对性能有极致要求,且不希望引入 Objective-C Runtime 依赖。
  • 都不选:

    • 在 iOS 10+ 的高性能场景,请直接使用 os_unfair_lock
    • 在 Swift 现代并发模型中,优先使用 Actors

总结

NSLockpthread_mutex 的一层“华丽外衣”。它牺牲了微小的性能,换取了 Cocoa 风格的便利性。