Swift中如何在concurrent enumerateObjects并发块中填充字典?
问题解答
直接结论
不可以直接在enumerateObjects(options: .concurrent)的并发执行块内对同一个字典/数组做写入操作。Swift 中的Dictionary、Array等原生集合类型默认都不是线程安全的,多个线程同时对同一个集合做写入操作时会发生内存竞争,你遇到的EXC_BAD_ACCESS就是典型的并发写导致的内存访问错误。
直接每次写入前加信号量/锁的方式会让所有写入操作退化成串行执行,完全抵消并发的性能收益,不符合性能优化的需求。

高性能解决方案
核心思路是避免多线程同时操作同一个字典,每个线程生成自己的局部字典,所有并发任务执行完成后再合并所有局部字典到最终结果里。全程只有将局部字典加入全局数组的阶段需要低频率加锁,几乎没有额外开销,能最大化利用并发性能。
优化后的示例代码如下:
struct Coordinate: Hashable { let x: Int let y: Int } struct CoordColor: Hashable{ let value: UIColor } func parseImgColor(_ cgImage: CGImage) -> [Coordinate : CoordColor] { let height = cgImage.height let width = cgImage.width var partialMaps: [[Coordinate : CoordColor]] = [] let lock = NSLock() // 用GCD的concurrentPerform替代NSArray的enumerateObjects,更符合Swift规范,性能更优 DispatchQueue.concurrentPerform(iterations: height) { y in var localMap: [Coordinate : CoordColor] = [:] // 内层循环直接串行遍历即可,不需要嵌套并发避免额外调度开销 for x in 0..<width { let coords = Coordinate(x: x, y: y) localMap[coords] = CoordColor(value: x % 2 == 0 ? .blue : .white) } // 仅在提交局部结果时加锁,加锁频率等于图片高度,和像素总数无关,开销可忽略 lock.lock() partialMaps.append(localMap) lock.unlock() } // 串行合并所有局部字典得到最终结果 var finalMap: [Coordinate : CoordColor] = [:] partialMaps.forEach { finalMap.merge($0) { (current, _) in current } } return finalMap }
方案优势
- 每个并发线程仅操作自己的局部字典,99%的执行逻辑不需要加锁,完全保留并发的性能收益
- 加锁操作仅在每个行处理完成后执行一次,频率极低,不会成为性能瓶颈
- 去掉了原来的NSArray桥接转换逻辑,减少了不必要的类型转换开销
- 去掉了嵌套的双层并发逻辑,避免了多余的线程调度开销,单层级并发已经可以拉满CPU所有核心的利用率
内容的提问来源于stack exchange,提问作者Paul Mayer
相关产品推荐
相关产品推荐

