Swift实现线程安全数组时,调用自定义DispatchQueue同步方法如何避免主线程死锁?
多线程数组读写的死锁修复方案
死锁触发的核心逻辑
当前实现的并发队列同步读逻辑本身不存在和主队列的必然耦合,出现死锁本质是出现了跨队列的循环同步等待:
- 自定义
annotationsQueue的块(读或屏障写)内部,存在DispatchQueue.main.sync的等待逻辑,需要等主队列任务执行完才能释放队列 - 主队列上的执行流程,刚好又同步访问
annotations属性,需要等annotationsQueue的锁释放才能返回结果
两边互相等对方释放资源,就会直接卡死,和观察到的主队列流程中触发死锁的堆栈表现完全匹配。
两种应对方案的对比与适用场景
- 不推荐强制将
annotationsQueue绑定后台线程
自定义并发队列本身是独立的执行上下文,不存在必须依赖特定线程才能运行的逻辑。单纯给队列加QoS标签或者强制切后台线程,根本解决不了循环等待的本质问题,反而会额外增加线程调度的开销。只要保证annotationsQueue上的所有执行块(同步读、屏障写)内部,永远不会用sync的方式等待主队列任务完成,哪怕在主队列上同步调用读属性,也不会触发死锁。 - 按需调整主队列的调用逻辑
如果主队列上的任务本身不需要同步拿到标注数组的结果,完全可以把同步读改成异步取数逻辑,在拿到数组副本的回调里再做UI更新,从根源上去掉主队列上的同步等待节点。但如果你的渲染逻辑必须同步拿到最新的标注数组才能完成布局,硬改成异步读反而会引入数组状态不同步的额外bug。
读属性实现的优化
当前的读属性代码存在强解包的冗余风险,可以简化成更安全的形式,不改变原有并发逻辑:
private var annotations: [MapAnnotation] { annotationsQueue.sync { unsafeAnnotations } }
必须规避的高风险写法
- 不要在
annotationsQueue的屏障写块里调用DispatchQueue.main.sync,这是触发当前死锁的最常见原因 - 不要为了简化队列逻辑把
annotationsQueue替换为主队列,否则所有数组操作都会阻塞UI,彻底失去并发读写的性能优势
内容的提问来源于stack exchange,提问作者Xernox
相关产品推荐
相关产品推荐

