Swift 5.9中如何并发排序数组的非重叠子范围?
解决Swift并发排序数组非重叠子范围的问题
错误原因分析
编译器报错是因为Swift的并发安全规则禁止在并发任务中直接捕获并修改值类型变量(这里的someArray是Array值类型)。更关键的是,数组的**写时复制(CoW)**特性会导致每个任务执行sort()时,都会复制整个数组的内容——这不仅完全抵消了并发的性能收益,还会造成巨大的内存开销。
可行解决方案
方案一:使用UnsafeMutableBufferPointer直接操作内存
通过将数组绑定到UnsafeMutableBufferPointer,可以直接操作底层连续内存,分割出非重叠的子缓冲区供并发任务排序,既绕开CoW,又保证并发安全(因为子缓冲区范围完全不重叠,无数据竞争)。
修改后的代码:
let arraySize = 1_000_000 var someArray = [Int]() someArray.reserveCapacity(arraySize) for _ in 0..<arraySize { someArray.append(.random(in: 0...1_000_000)) } let divisions = 6 var divisionLength = arraySize / divisions if arraySize % divisions != 0 { divisionLength += 1 } // 将数组绑定到可变缓冲区指针 someArray.withUnsafeMutableBufferPointer { buffer in Task { await withTaskGroup(of: Void.self) { taskGroup in var currentIndex = 0 while currentIndex < buffer.count { let sliceStart = currentIndex let sliceEnd = min(currentIndex + divisionLength, buffer.count) currentIndex = sliceEnd // 捕获子缓冲区的范围,复制索引值避免闭包捕获可变变量 taskGroup.addTask { let subBuffer = buffer[sliceStart..<sliceEnd] subBuffer.sort() } } await taskGroup.waitForAll() } } }
方案二:手动拆分数组为独立子数组并发排序后合并
如果不想使用指针操作,可以先将原数组拆分为多个独立的子数组,并发排序后再合并回原数组。这种方式逻辑更直观,仅会产生子数组的少量内存开销:
let arraySize = 1_000_000 var someArray = [Int]() someArray.reserveCapacity(arraySize) for _ in 0..<arraySize { someArray.append(.random(in: 0...1_000_000)) } let divisions = 6 var divisionLength = arraySize / divisions if arraySize % divisions != 0 { divisionLength += 1 } // 拆分原数组为子数组切片 var subArrays: [ArraySlice<Int>] = [] var currentIndex = 0 while currentIndex < someArray.endIndex { let end = min(currentIndex + divisionLength, someArray.endIndex) subArrays.append(someArray[currentIndex..<end]) currentIndex = end } // 并发排序每个子数组并合并结果 let sortedSubArrays = await withTaskGroup(of: [Int].self) { taskGroup in for subArray in subArrays { taskGroup.addTask { subArray.sorted() } } var results = [Int]() results.reserveCapacity(arraySize) for await sortedSub in taskGroup { results.append(contentsOf: sortedSub) } return results } // 替换原数组 someArray = sortedSubArrays
关键注意事项
- 无论哪种方案,必须保证子范围完全非重叠,否则会出现数据竞争或排序结果错误。
- 使用指针操作时,
withUnsafeMutableBufferPointer会自动管理指针生命周期,避免野指针问题,无需手动释放。 - 方案二的合并步骤是串行的,但排序阶段是并发的,对于大规模数组依然能获得明显的性能提升。
内容的提问来源于stack exchange,提问作者JMA
相关产品推荐
相关产品推荐

