Swift基于DispatchSemaphore的线程安全数组跨线程传数据死锁咨询
问题根源分析
1. 信号量不可重入导致的内部死锁
你最初的实现中,unshift、pop方法执行时已经通过wait()获取了信号量,方法内部调用的count()又会再次尝试获取同一个信号量。DispatchSemaphore属于不可重入锁,值为1的信号量被持有后,同一个线程再次调用wait()会直接进入永久等待状态,这是初始版本死锁的核心原因。
你后续修改count方法的加锁逻辑虽然解决了重入问题,但仍然存在第二个设计缺陷:
2. 消费线程空转抢占锁,导致生产线程无法获取资源
你的消费逻辑采用while(true)死循环实现,当数组为空时,unshift方法仍然会高频执行,持续抢占、释放信号量,高并发场景下生产线程的append操作会始终抢不到信号量,表现为双向卡住的类死锁状态。
修复方案
基础修复版本
首先调整SyncArray内部逻辑,持有锁的场景下直接访问内部变量,不要调用其他加锁方法避免重入,同时把内部存储数组改为私有禁止外部直接访问:
public class SyncArray<T> { // 改为私有,避免外部直接操作导致线程不安全 private var dataArray = [T]() private var semaphore = DispatchSemaphore(value: 1) public init() {} private func wait() { semaphore.wait() } private func signal() { semaphore.signal() } public func count() -> Int { wait(); defer { signal() } return dataArray.count } public func unshift() -> T? { wait(); defer { signal() } // 持有锁的状态下直接访问内部数组,无需调用加锁的count方法 guard !dataArray.isEmpty else { return nil } return dataArray.removeFirst() } public func pop() -> T? { wait(); defer { signal() } guard !dataArray.isEmpty else { return nil } return dataArray.popLast() } public func append(value: T) { wait(); defer { signal() } dataArray.append(value) } }
其次优化消费线程逻辑,数组为空时增加短暂休眠,避免无意义的锁抢占:
DispatchQueue.global().async { while true { if let data = buff.unshift() { // 此处添加数据处理逻辑 processData(data) } else { // 空数组时休眠1毫秒,降低锁抢占频率 Thread.sleep(forTimeInterval: 0.001) } } }
更高效的优化方案(可选)
如果需要避免空轮询的性能损耗,可以用DispatchCondition替代信号量实现生产者-消费者模型,数组为空时消费线程自动阻塞,有新数据时再被唤醒,不需要空转占用CPU资源。
内容的提问来源于stack exchange,提问作者Olexander Korenyuk
相关产品推荐
相关产品推荐

