WWDC21-Concurrency-BehindTheScenes

1.Threading Model

1.1 GCD code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ }
func updateDatabase(with articles: [Article], for feed: Feed) { /* ... */ }

let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: concurrentQueue)

for feed in feedsToUpdate {
let dataTask = urlSession.dataTask(with: feed.url) { data, response, error in
// ...
guard let data = data else { return }
do {
let articles = try deserializeArticles(from: data)
databaseQueue.sync { // 可能导致产生很多线程
updateDatabase(with: articles, for: feed)
}
} catch { /* ... */ }
}
dataTask.resume()
}
  • cpu的核心有限,但是线程可能会创建很多,cpu在维护线程切换需要花费很大的开销,可能导致内存和队列溢出
    • 每个线程都有一个栈和对应的kernel data structure来追踪线程,有的线程可能会持有其他线程需要的锁。
    • 大量的线程和有限的核心导致频繁的线程切换

1.2 Swift Concurrency Code

  • 每个核心仅有一个线程

    • 不存在线程切换
  • 每个线程只需要维护continuation,

    • 需要的消耗只有函数调用
  • swift语言特性

    1. await和非阻塞的线程

      • 同步函数调用:每个线程都有个调用栈,存储调用的函数
      • 异步函数调用:在栈和堆都会存储调用信息。在栈中存储非异步信息,堆中存储异步需要的信息

      image-20210704214116623

    2. swift task模型中的依赖追踪

      • continuation:是在async函数之后执行的内容
      • CooperativeThreadPool
        • 线程数就是CPU核心数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ }
func updateDatabase(with articles: [Article], for feed: Feed) async { /* ... */ }

await withThrowingTaskGroup(of: [Article].self) { group in
for feed in feedsToUpdate {
group.async {
let (data, response) = try await URLSession.shared.data(from: feed.url)
// ...
let articles = try deserializeArticles(from: data)
await updateDatabase(with: articles, for: feed)
return articles
}
}
}

1.3 实践

  • concurrency也有消耗,要确保使用concurrency的收益超过管理他的消耗
  • profile查看代码效率
  • await前后的线程可能不是同一个线程(不遵循原子性),因此在await前后不要持有lock
  • 不要使用unsafe primitives
    • 使用await、actors、TaskGroups是安全的,编译器也会检查
    • 使用os_unfair_lock、NSLock等也是安全的,但是编译器不会检查
    • 使用DispatchSemaphore等是不安全的

image-20210704220718545

  • 验证app中是否使用unsafe primitives
    • 设置debug环境下的变量LIBDISPATCH_COOPERATIVE_POOL_STRICT = 1

image-20210704221844472

2.Synchronization

2.1 Mutual Exclusion(互斥)

  • 避免数据竞争
  • actor在有竞争情况下会复用线程,没有竞争的情况下也不会阻塞,而是执行其他任务。

image-20210704222957998

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-->
0%