DEV Community

Ting
Ting

Posted on

如何保证TaskGroup中task的顺序

引言

TaskGroup 是 Swift 5.4 左右新引入的 Swift Concurrency 中的一个概念。在新的 Swift Concurrency 模型中,暂时还没有类似于过去在 Dispatch framework 中的 class func concurrentPerform(iterations: Int, execute: (Int) -> Void) 方法。因此,Swift Concurrency 只能算得上支持 concurrency “并发”的语言特性,而不能算支持 parallelism “并行”计算的功能。

Concurrency vs Parallelism

对了,如果你不是大牛的话,我毛遂自荐一下自己拍脑门想出来的用一句话来区分 concurrency 和 parallelism 的“金句”——

parallelism 是一堆任务在一堆资源上运行,concurrency 是一堆任务同时运行。

举个例子,如果你有一颗单核的奔腾四处理器,但是想要运行一个桌面系统,肯定会有很多任务需要处理器同时解决,才能让你一边听歌、一边上网冲浪。这时候,一个核心调度许多线程的思想,就是 concurrency —— 把运行时切片,干几毫秒这个事情,再干几毫秒那个事情,从而造成一种所有任务都在流畅运作的“假象”。

过了十年,现在你有一颗八核的酷睿i7处理器,想干的事情也多了很多:一边玩儿游戏、一边直播、一边听歌、一边在后台渲染视频……这时候,受到物理规律限制,再牛的单核处理器也没法同时运算这么多任务了,那就只好依靠 parallelism 的思想,通过非常聪明的调度算法,把可以互不相干同时运行的任务,分配到不同的“核”上,从而实现同时执行8件事、16件事(英特尔超线程)等等。

如何确定顺序?

用过 DispatchGroup API 的朋友都知道,可以用 enter()/leave()方法来告知一个子任务的开始和结束。当所有的enter/leave信号都被group收到时,有notify方法来通知任意队列来执行接下来的操作。

然而,子任务完成的顺序是不确定的。大体上任务完成的顺序和开始执行的顺序正相关,但有可能有的处理器核心比较忙,有的不太忙,从而造成有些子任务的完成顺序发生了颠倒。这时,就有各种方法来维持任务的顺序了。

  • 对于各自独立的任务而言,最简单直观的方法就是预先分配一个与任务数相等的定长数组,当开启任务时,给每个任务分配一个序列号。结束任务时通过序列号将结果插入到数组的对应位置;

  • 对于各自不完全独立的任务而言,在 Dispatch 这个库里最常用的是 DispatchSemaphore 类,即一个通过计数来约束线程阻塞的信号量。当然,阻塞线程必然会拖慢运行速度,但是为了确保顺序和其他的一致性,这是无法避免的代价。

在新的 Swift Concurrency 中的 TaskGroup 类型和 DispatchGroup 存在一定相似之处。它有 async 的 waitForAll() 方法来实现等待所有子任务完成的功能,但更多时候则是用一个 for-await-in 的 async 循环来接受子任务完成后返回的结果。

在这里同样会遇到顺序问题。除了传统的下标数组的办法外,还有一种类似于传统的 DispatchSerialQueue 的思路,即:

for item in collection {
    async let result = await asyncFunction(item)
    results.append(result)
}
Enter fullscreen mode Exit fullscreen mode

虽说这样使得各个任务没法“并发”地运行,从而效率大大降低,但还是能保证各个任务的顺序确定。

Top comments (0)