从层级结构上来说,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的速度通常比NSLock快 2 倍左右。虽然这个量级在普通业务中可以忽略不计,但在底层高性能库中至关重要。
4. Swift 时代的变革
在 Swift 中,NSLock 的使用频率正在下降,原因如下:
- Swift 并不原生支持 Objective-C 异常:
NSLock无法像@synchronized那样自动处理异常安全。 - 指针开销: Swift 访问
pthread_mutex需要处理UnsafeMutablePointer,虽然繁琐但性能极高。 - 替代者的崛起:
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。
- 在 iOS 10+ 的高性能场景,请直接使用
总结
NSLock 是 pthread_mutex 的一层“华丽外衣”。它牺牲了微小的性能,换取了 Cocoa 风格的便利性。