在 Objective-C 运行时中大量使用自旋锁,主要有以下几个原因:
1. 性能考虑
上下文切换成本
// 自旋锁实现
static ALWAYS_INLINE void OSSpinLockLock(volatile OSSpinLock *lock) {
do {
while (lock->value != 0) {
__asm__ volatile ("pause"); // 不释放CPU,继续尝试
}
} while (!OSAtomicCompareAndSwap32(0, 1, &lock->value));
}
// 相比互斥锁的实现
pthread_mutex_lock(&mutex); // 可能导致线程休眠和上下文切换
// ...
pthread_mutex_unlock(&mutex);
优势:
- 避免了线程上下文切换的开销
- 适合短期持有的场景
- 在多核处理器上效率更高
2. 使用场景特点
短暂的临界区
// 属性访问的典型场景
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (!atomic) return *((id *)((char *)self + offset));
spinlock_t& slotlock = PropertyLocks[GOODHASH(offset)];
slotlock.lock(); // 持锁时间极短
id value = *((id *)((char *)self + offset));
slotlock.unlock();
return value;
}
特点:
- 锁的持有时间非常短
- 竞争不激烈
- 代码路径简单
3. 内存效率
结构简单
typedef struct {
volatile int32_t value; // 仅需要一个32位整数
} OSSpinLock;
// 相比互斥锁的复杂结构
struct pthread_mutex_t {
// ... 更复杂的内部结构
// 包含条件变量、等待队列等
};
优势:
- 内存占用小
- 缓存友好
- 初始化成本低
4. 适用的情况
理想场景
// 引用计数操作
inline bool objc_object::sidetable_tryRetain() {
SideTable& table = SideTables()[this];
bool result = false;
table.lock(); // 快速的加锁解锁
// 简单的引用计数操作
table.unlock();
return result;
}
最佳实践:
- 临界区执行时间短
- 线程等待时间短
- CPU资源充足
5. 潜在问题
优先级反转
// 可能出现的问题场景
while (lock->value != 0) {
// 如果持有锁的是低优先级线程
// 而等待的是高优先级线程
// 可能导致优先级反转
__asm__ volatile ("pause");
}
解决方案:
- iOS 10 后系统更多使用 os_unfair_lock
- 对于复杂场景使用互斥锁
- 需要考虑优先级时使用其他锁机制
6. 使用建议
适合使用自旋锁的场景
// 1. 简单的原子操作
atomic_property.lock();
value = _property;
atomic_property.unlock();
// 2. 快速的引用计数操作
spinlock.lock();
refCount++;
spinlock.unlock();
不适合使用自旋锁的场景
// 1. 复杂的操作
lock.lock();
[self complexOperation]; // 耗时操作
lock.unlock();
// 2. 可能阻塞的操作
lock.lock();
[self operationMayBlock]; // 可能阻塞
lock.unlock();
7. 总结
自旋锁在 Objective-C 运行时中的广泛使用是基于以下考虑:
- 性能优化:避免上下文切换
- 场景匹配:适合短期、快速的操作
- 资源效率:内存占用小,初始化快
- 实现简单:容易维护和调试
- 硬件友好:在现代多核处理器上表现良好
但需要注意:
- 不适合长时间持有
- 要考虑优先级反转问题
- iOS 10 后推荐使用 os_unfair_lock
- 复杂场景应考虑其他锁机制