以下是我如何使用withTaskGroup
的简化版本:
let allResults = await withTaskGroup(of: ([Double], Float, Float, Int).self,
returning: FinalResult.self,
body: { taskGroup in
for operation in operations {
taskGroup.addTask { @MainActor in
let value = await operation.execute()
return value
}
}
// Collect child task results here...
})
execute
函数看起来如下,其中过程包括相当密集的数学计算任务,如机器学习模型的特征提取:
func execute() async -> ([Double], Float, Float, Int) {
async let result0: [Double] = process0() ?? []
async let result1: Float = process1() ?? -1
async let result2: Float = process2()
async let result3: Int = process3()
return (await result0, await result1, await result2, await result3)
}
我遇到的问题是,最终的结果是不一致的,有时我得到适当的结果,有时不是。一个例子是,有时我得到一个512个特征(数组长度)的全零数组,而它们应该是非零数字。相反,如果我简单地使用for loop
而不是withTaskGroup
,结果似乎更一致。
所以我想知道withTaskGroup
和async let
一起使用是否有任何后果,因为这是并行的并行。与并发性不同,我对并行性的理解是分配CPU的每个核心来同时处理任务,但是如果我提供4个operations
,这意味着4 x 4 = 16个并行进程,这比可用的核心数量要多。
这绝对不是问题所在。问题更有可能是您在process1()
等中的代码不是线程安全的(例如,依赖于从非线程安全的属性读取的数据,或者以无效的方式操作返回的Array)。如果您正确地编写了代码,那么超过内核数量并不会导致逻辑问题。它可能会导致性能问题,这取决于许多因素,但它不会导致正确性问题。
你可能想要打开"严格并发检查";在您的构建设置中,这可以帮助您发现错误。(它也可以在Foundation中发现很多你无法修复的问题,所以有时最好在独立的代码或较小的项目中使用它。)