为何Swift Actor内部的withTaskGroup任务能并行执行?
Actor内部async let串行、withTaskGroup并行的原因及内部同步机制
一、为什么async let串行,withTaskGroup却能并行?
核心差异在于任务是否绑定Actor的隔离上下文:
async let的串行逻辑:
在Actor内部使用async let创建的任务,默认会继承当前Actor的隔离上下文,也就是说这些任务会被调度到该Actor的专属串行执行器上执行。Actor的串行执行器保证同一时间只有一个任务访问其隔离的可变状态,所以多个async let任务只能排队依次运行,表现为串行。
示例代码:actor Actor1 { func runSerialTasks() async { async let task1 = heavyWork() async let task2 = heavyWork() let _ = await (task1, task2) // task1和task2串行执行 } func heavyWork() async -> Int { print("Start work") try? await Task.sleep(nanoseconds: 1_000_000_000) return 0 } }withTaskGroup的并行逻辑:
withTaskGroup的任务调度更灵活:如果添加到组内的任务不访问Actor的隔离可变状态,Swift并发运行时会将这些任务分配到全局并发池的线程上执行,自然能实现并行。
只有当任务需要访问Actor的隔离状态时,才会自动切换回Actor的串行执行器,此时涉及隔离状态的代码会串行,但不涉及的任务仍可并行。
示例代码:actor AnActor { func runParallelTasks() async { await withTaskGroup(of: Int.self) { group in group.addTask { print("Group task 1 start") try? await Task.sleep(nanoseconds: 1_000_000_000) return 1 // 无隔离状态访问,并行执行 } group.addTask { print("Group task 2 start") try? await Task.sleep(nanoseconds: 1_000_000_000) return 2 // 无隔离状态访问,并行执行 } } } }
二、Actor内部异步并行任务的同步机制
Actor内部的异步任务同步核心围绕隔离上下文的控制和串行执行器的调度:
- 默认隔离继承:所有在Actor内部创建的异步任务(如
async let),默认会绑定当前Actor的隔离上下文,必须通过Actor的串行执行器执行,以此保证对隔离可变状态的访问是线程安全的,避免数据竞争。 - 主动脱离隔离:如果需要创建并行任务,可以通过两种方式:
- 使用
Task.detached显式创建脱离当前Actor隔离的任务,这类任务直接在全局并发池运行,但如果要访问Actor的隔离状态,必须通过await self.someIsolatedMethod()将任务调度回串行执行器。 - 在
withTaskGroup中添加不访问隔离状态的任务,运行时自动将其分配到全局并发池实现并行。
- 使用
- 隔离状态访问的安全调度:无论任务是内部还是外部创建,只要需要访问Actor的隔离可变状态,都会被调度到Actor的串行执行器排队执行,同一时间只有一个任务能操作隔离状态,这是Actor实现线程安全的核心机制。
内容的提问来源于stack exchange,提问作者MaxFactorHub
相关产品推荐
相关产品推荐

