使用DispatchQueue.global().sync方法时出现数据竞争崩溃问题求助
关于Set线程安全的问题分析与解决
你当前的写法存在线程安全隐患,问题出在这两点:
- 你用的
DispatchQueue.global()是并发队列,即使通过sync执行remove操作,也只能保证这个remove任务在队列里执行,但如果其他线程直接修改fetchingInProgressList,或者用其他队列操作这个集合,依然会出现多线程并发访问的冲突。 - 线程安全的核心是所有对共享变量的读写操作都必须串行化,也就是同一时间只能有一个线程操作它,而你只给remove加了队列包裹,其他操作如果没做同样处理,照样不安全。
正确的实现方式
推荐用串行队列来包裹所有对fetchingInProgressList的操作,示例代码如下:
// 创建专门用于访问共享集合的串行队列 private let accessQueue = DispatchQueue(label: "com.yourapp.fetchingListAccess") private var fetchingInProgressList: Set<String> = [] // 添加元素 func addFetchingTask(_ taskId: String) { accessQueue.async { self.fetchingInProgressList.insert(taskId) } } // 删除元素 func removeFetchingTask(_ taskId: String) { accessQueue.sync { _ = self.fetchingInProgressList.remove(taskId) } } // 查询是否在执行中 func isTaskFetching(_ taskId: String) -> Bool { return accessQueue.sync { self.fetchingInProgressList.contains(taskId) } }
补充说明
如果追求更高的读写效率,也可以用并发队列配合barrier:写操作(insert/remove)用async(flags: .barrier),读操作直接用async或sync,这样多个读操作可以并发执行,写操作会等待所有读完成后执行,且写过程中不会有其他读写操作干扰。示例如下:
private let accessQueue = DispatchQueue(label: "com.yourapp.fetchingListAccess", attributes: .concurrent) private var fetchingInProgressList: Set<String> = [] // 写操作(用barrier保证串行) func addFetchingTask(_ taskId: String) { accessQueue.async(flags: .barrier) { self.fetchingInProgressList.insert(taskId) } } func removeFetchingTask(_ taskId: String) { accessQueue.async(flags: .barrier) { _ = self.fetchingInProgressList.remove(taskId) } } // 读操作(可并发) func isTaskFetching(_ taskId: String) -> Bool { return accessQueue.sync { self.fetchingInProgressList.contains(taskId) } }
核心原则就是:所有对共享可变变量的操作,都必须通过同一个同步机制来管控,不能有任何直接访问的情况。
内容的提问来源于stack exchange,提问作者LiangWang
相关产品推荐
相关产品推荐

