使用async-await任务组的最大线程数

21

我的目的是了解 Swift 5.5 的 async-await 所使用的“协作式线程池”,以及任务组如何自动约束并发度:考虑以下任务组代码,同时进行 32 次计算:

func launchTasks() async {
    await withTaskGroup(of: Void.self) { group in
        for i in 0 ..< 32 {
            group.addTask { [self] in
                let value = doSomething(with: i)
                // do something with `value`
            }
        }
    }
}

虽然我希望它能像广告中所说的那样限制并发程度,但我每次只能同时处理两个任务。这比我预期的要受到更多限制:

enter image description here

如果我使用旧版的GCD concurrentPerform ...

func launchTasks2() {
    DispatchQueue.global().async {
        DispatchQueue.concurrentPerform(iterations: 32) { [self] i in
            let value = doSomething(with: i)
            // do something with `value`
        }
    }
}

...我一次获取12个,充分利用设备(iOS 15模拟器在我的6核i9 MacBook Pro上),同时避免线程爆炸:

enter image description here

顺便提一下,这两个都是在运行于Big Sur的Xcode 13.0 beta 1(13A5154h)中进行了剖析。请忽略这两次运行中各自“作业”之间的微小差异,因为所涉及的函数只是在随机时间内旋转;关键观察点是并发度是我们预期的。

这个新的async-await(以及任务组)自动限制了并行度,这非常棒,但是async-await的协作线程池比我预期的要受到更多限制。我看不到任何调整该池参数的方法。我们如何更好地利用硬件而避免线程爆炸(而不使用像非零信号量或操作队列等旧技术)?

1个回答

18

在 Xcode 14.3 中,协作线程池在模拟器上的限制已被移除(发布说明中没有提到此更改)。


看起来这种奇怪的行为是 Xcode 14.2 及更早版本模拟器的一个限制。如果我在我的实体 iPhone 12 Pro Max 上运行它,使用 async-await 任务组方法会导致 6 个并发任务...

enter image description here

这与concurrentPerform的行为本质上是相同的:

enter image description here

行为,包括并发程度,在物理设备上本质上是相同的。

人们可以推断出,模拟器似乎被配置为限制async-await,而比直接使用GCD调用所能实现的更多。但在实际的物理设备上,async-await任务组的行为与预期相同。


值得一提的是,上面的内容是在MacBook Pro上使用Xcode 13生成的。我在两个不同的Mac上使用Xcode 14.2重复了这个过程,并得到了不同的结果。具体来说,在我的2018年英特尔MacBook Pro模拟器中,协作线程池有两个线程。但是,在我的2022 Mac Studio的模拟器中,它被限制在3个线程:

enter image description here

看起来模拟器的合作线程池大小受到使用的 Mac 硬件的影响。但关键是,模拟器上的协作线程池受到人为限制。


为了比较,这里是在Xcode 14.2上运行在实体iPhone 12 Pro Max上的一个可比较的“兴趣点”示例。

enter image description here


1
据我正确理解,新的协作线程池与之前的不同之处在于,它不会过度提交系统(每个核心只运行一个线程)。这使我们可以摆脱空闲线程和不必要的上下文切换。但是从您提供的concurrentPerform解决方案的截图来看,GCD似乎以完全相同的方式进行了优化。是这样吗? - V.V.V
是的,concurrentPerform 和新的协作线程池方法都限制了线程数量,避免了线程爆炸。我认为,主要的好处在于这样可以避免死锁,因为线程爆炸会耗尽 GCD 非常有限的工作线程池。 - Rob
“concurrentPerform”当前线程数与“processorCount”/“activeProcessorCount”的结果相同,但在模拟器上,协作池不同。但在物理设备上是相同的。 - Rob
1
有很多不同的变量会影响两个环境中速度差异,所以我不愿意做出如此广泛的陈述,但是,能够充分利用协作线程池的代码将能够在物理设备上享受更高程度的并发性,而不是在模拟器上。在基准测试性能时,没有什么可以替代在物理设备上进行测试。 - Rob
2
@Alexander - 是的,Swift并发协作线程池受核心数限制,就像旧的GCD concurrentPerform一样。回到那个旧的GCD API真的没有任何好处,而且有很多理由不这样做。话虽如此,演员是可重入的,网络请求根本不受线程限制。坦白地说,我们经常遇到相反的问题,即我们故意想将网络请求限制在4-8个,以减少峰值内存使用和最小化服务器影响。做更多的事情几乎没有性能上的好处,但会引入问题。 - Rob
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接