1.Threading Model
1.1 GCD code
1 | func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ } |
- cpu的核心有限,但是线程可能会创建很多,cpu在维护线程切换需要花费很大的开销,可能导致内存和队列溢出
- 每个线程都有一个栈和对应的kernel data structure来追踪线程,有的线程可能会持有其他线程需要的锁。
- 大量的线程和有限的核心导致频繁的线程切换
1.2 Swift Concurrency Code
每个核心仅有一个线程
- 不存在线程切换
每个线程只需要维护continuation,
- 需要的消耗只有函数调用
swift语言特性
await
和非阻塞的线程- 同步函数调用:每个线程都有个调用栈,存储调用的函数
- 异步函数调用:在栈和堆都会存储调用信息。在栈中存储非异步信息,堆中存储异步需要的信息
swift task模型中的依赖追踪
- continuation:是在async函数之后执行的内容
- CooperativeThreadPool
- 线程数就是CPU核心数
1 | func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ } |
1.3 实践
- concurrency也有消耗,要确保使用concurrency的收益超过管理他的消耗
- profile查看代码效率
- await前后的线程可能不是同一个线程(不遵循原子性),因此在await前后不要持有lock
- 不要使用unsafe primitives
- 使用await、actors、TaskGroups是安全的,编译器也会检查
- 使用os_unfair_lock、NSLock等也是安全的,但是编译器不会检查
- 使用DispatchSemaphore等是不安全的
- 验证app中是否使用unsafe primitives
- 设置debug环境下的变量LIBDISPATCH_COOPERATIVE_POOL_STRICT = 1
2.Synchronization
2.1 Mutual Exclusion(互斥)
- 避免数据竞争
- actor在有竞争情况下会复用线程,没有竞争的情况下也不会阻塞,而是执行其他任务。
2.2 Reentrancy & Prioritization
2.3 Main actor
Main actor可能导致上下文频繁切换
func loadArticle(with id: ID) async throws -> Article { /* ... */ } @MainActor func updateUI(for article: Article) async { /* ... */ } @MainActor func updateArticles(for ids: [ID]) async throws { for id in ids { let article = try await database.loadArticle(with: id) // other actor await updateUI(for: article) // main actor } } <!--2-->