在Swift中,为确保线程安全,通常使用GCD(Grand Central Dispatch)和队列(DispatchQueue)、NSLock,以及现代机制,例如actor(从Swift 5.5开始)。
基本思想 – 访问可变数据要么严格通过顺序队列,要么使用同步器(锁)。
GCD示例:
class ThreadSafeArray<Element> { private var array: [Element] = [] private let queue = DispatchQueue(label: "com.example.arrayQueue", attributes: .concurrent) func append(_ item: Element) { queue.async(flags: .barrier) { self.array.append(item) } } func get(index: Int) -> Element? { var result: Element? queue.sync { result = self.array.indices.contains(index) ? self.array[index] : nil } return result } }
现代方法:Actors (Swift 5.5+):
actor SafeCounter { private var value = 0 func increment() { value += 1 } func get() -> Int { return value } }
这两种方法都可以避免数据竞争并保持状态的一致性。
问题:
如果您在主队列中使用
DispatchQueue.sync,会发生什么情况,以及为什么?
答案: 会发生死锁(Deadlock),因为主队列已经在执行任务,并等待在同一队列上的同步操作完成。因此,队列无法处理下一项任务,直到当前任务完成,而这将永远不会发生。
示例:
DispatchQueue.main.sync { // 死锁:这行代码永远不会执行 }
故事
在项目中使用了可变集合,并且没有进行同步地同时从多个线程访问。这导致应用程序崩溃和由于数据竞争造成的难以捕捉的错误:元素偶尔会丢失,有时会发生数组越界。
故事
开发者在运行时错误地在主队列上使用DispatchQueue.sync,导致死锁并导致用户界面完全停止。在更新发布后,急需修复并回滚版本。
故事
在尝试使用NSLock使类线程安全时,忘记为所有退出路径实现lock/unlock(例如,在错误或guard内部的return情况下),这导致死锁潜能以及严重的应用程序性能问题。