线程安全
问题
iOS 中有哪些保证线程安全的方式?各种锁的性能和适用场景?
答案
常见线程安全方案
| 方案 | 类型 | 性能 | 特点 |
|---|---|---|---|
| os_unfair_lock | 自旋→互斥 | ⭐⭐⭐⭐⭐ | iOS 10+,最快的锁 |
| pthread_mutex | 互斥锁 | ⭐⭐⭐⭐ | POSIX 标准 |
| NSLock | 互斥锁 | ⭐⭐⭐ | ObjC 封装 |
| NSRecursiveLock | 递归锁 | ⭐⭐⭐ | 同线程可重入 |
| DispatchQueue(serial) | 串行队列 | ⭐⭐⭐ | GCD 串行化 |
| dispatch_barrier | 屏障 | ⭐⭐⭐ | 读写锁效果 |
| Actor | Swift 并发 | ⭐⭐⭐⭐ | 编译器保证 |
串行队列实现线程安全
class ThreadSafeArray<T> {
private var array: [T] = []
private let queue = DispatchQueue(label: "com.app.threadSafeArray")
func append(_ element: T) {
queue.async { self.array.append(element) }
}
func getAll() -> [T] {
queue.sync { return array }
}
}
读写锁(dispatch_barrier)
class ReadWriteCache<Key: Hashable, Value> {
private var dict: [Key: Value] = [:]
private let queue = DispatchQueue(label: "com.app.cache", attributes: .concurrent)
func read(key: Key) -> Value? {
queue.sync { dict[key] } // 读:并发执行
}
func write(key: Key, value: Value) {
queue.async(flags: .barrier) { // 写:独占执行
self.dict[key] = value
}
}
}
NSLock
class Counter {
private var count = 0
private let lock = NSLock()
func increment() {
lock.lock()
defer { lock.unlock() }
count += 1
}
}
死锁风险
NSLock 不支持递归加锁——如果在同一线程上重复 lock 会死锁。需要递归锁时使用 NSRecursiveLock。
@Sendable 与线程安全检查
// Swift 6 严格并发检查
func process(_ data: @Sendable () -> Void) {
Task { data() }
}
// 非 Sendable 类型不能跨并发域传递
class MutableState { // ⚠️ 编译器警告
var value = 0
}
常见面试问题
Q1: OSSpinLock 为什么被废弃?
答案:自旋锁存在优先级反转问题——低优先级线程持有锁时,高优先级线程自旋等待,CPU 一直调度高优先级线程,低优先级线程得不到执行机会,无法释放锁。os_unfair_lock 替代了它,会在等待时让出 CPU。
Q2: atomic 和线程安全的关系?
答案:ObjC 的 atomic 属性保证 getter/setter 是原子操作(不会读到写一半的值),但不保证线程安全。例如 self.array 是 atomic,但 [self.array addObject:] 不是原子操作——读取和修改之间可能被其他线程插入操作。
Q3: Swift 有 atomic 属性吗?
答案:Swift 没有内置的 atomic 属性关键字。线程安全需要手动使用锁、队列或 Actor。从 Swift 6 开始,编译器通过 Sendable 检查在编译时发现潜在的数据竞争。