Swift并发编程:await是怎么实现非阻塞式挂起的

193 阅读5分钟

await 的非阻塞式挂起机制是基于 协程事件循环(event loop)等底层概念实现的。在 Swift 中,await 并不会像传统的阻塞式调用那样让线程停住,而是通过将当前任务挂起(suspended)并允许其他任务继续执行来实现非阻塞效果。具体来说,await 让你在等待结果的同时不会阻塞线程,允许其他任务继续并发执行。

我们可以从以下几个角度来理解 await 的工作原理:


1️⃣ 挂起与恢复

- 挂起(Suspension)

当你调用一个 async 函数并在某个位置使用 await 时,这个 await 会告诉编译器当前函数可能会被挂起。这个函数暂停执行,释放当前线程的控制权,让其他任务得以执行。

- 恢复(Resumption)

当挂起的任务完成时(例如,异步网络请求、延时操作等),系统会恢复任务的执行,继续执行 await 后面的代码。

这其实是一个 状态机 的机制,任务会被挂起,在异步操作完成后恢复。


2️⃣ 事件循环与任务队列

为了让 await 非阻塞,Swift 使用了 事件循环 或者说 任务队列 来调度任务。当你在 async 函数内部使用 await 时,编译器会将当前任务(函数)加入一个队列,而不是让当前线程挂起。

事件循环/任务队列

  1. 当你调用一个异步函数(例如 async letawait fetchData())时,任务被加入到 事件循环任务队列
  2. 当前线程(通常是主线程)会继续执行其它代码,不会被阻塞。
  3. 当异步操作完成时,事件循环会恢复这个任务并继续执行。这个恢复的操作会在其他任务完成后触发。

3️⃣ 使用 Task 和 Await 的实现

我们先来看一个非常简单的 async/await 示例:

swift
复制编辑
func fetchData() async -> String {
    // 模拟异步操作,例如从网络获取数据
    await Task.sleep(2 * 1_000_000_000) // 模拟延时操作
    return "数据加载完毕"
}

Task {
    print("开始获取数据...")
    let result = await fetchData() // 这里会挂起当前任务,等待结果
    print(result)
}

在上面的代码中:

  1. fetchData() 是一个异步函数,调用它时会挂起当前的 Task
  2. await 会将当前 Task 暂停,并允许其他代码在同一线程中执行。
  3. Task.sleep() 完成后,Task 会恢复,返回 "数据加载完毕"

这里,Task.sleep() 是一种模拟操作,它并没有阻塞当前线程。而是将控制权交还给了运行时,执行其他任务,直到 sleep 完成后,原来的任务才会继续。


4️⃣ 底层实现

- 挂起和恢复的机制

await 的非阻塞式挂起通常通过 回调状态机上下文切换 实现。底层通常会用类似于 纤程(fiber)协程 的机制来管理这个任务的暂停和恢复。具体过程如下:

  1. 挂起:当一个 await 被调用时,函数会返回给调用者一个 “挂起点” ,并且将当前任务加入到队列中。这个任务的状态会被保存,等到结果返回时,任务再从队列中取出。
  2. 恢复:一旦异步操作完成,底层调度器会通知相关的任务恢复,重新调度任务的执行。这是通过事件循环来实现的,异步操作会将事件触发的回调加入到队列中,等待当前线程空闲时执行。

- 任务队列

任务的调度机制非常类似于事件循环,它是 基于队列的,意味着你不需要等待每一个任务的执行结果,而是将它们按照顺序加入到队列中,任务会在后续空闲时执行。

例如,在以下代码中:

swift
复制编辑
Task {
    let result1 = await fetchData() // 任务1
    let result2 = await fetchData() // 任务2
}
  1. 任务 1 会被加入任务队列,并挂起。
  2. 任务 1 挂起期间,其他任务可以继续执行。
  3. 任务 1 完成时,队列会恢复 任务 2,并且继续执行。

- 线程池和异步任务

await 的执行过程中,线程池会帮助管理任务的执行。线程池通过 调度器优先级队列 来确保异步任务能在合适的时机恢复并继续执行。


5️⃣ 核心原理:

1. 任务挂起(Suspension)

  • 当你调用 await,当前的函数被挂起,等待异步任务完成。
  • 这时,当前线程会继续执行其他任务,并不会被阻塞。

2. 任务恢复(Resumption)

  • 一旦异步操作完成(比如网络请求返回),被挂起的任务会从 挂起状态 恢复,继续执行后续代码。

3. 任务队列

  • 异步操作完成后,任务恢复的执行顺序是由事件循环控制的。通过事件循环,任务按顺序执行,避免了死锁和不必要的等待。

6️⃣ 总结

  • await 本质上是通过 任务队列状态机 来管理任务的挂起和恢复,它实现了非阻塞的异步执行。
  • 通过 await,我们可以暂停当前的任务,释放当前线程的控制权,让其他任务得以执行。
  • 底层通过 回调事件循环 机制来确保任务的有序执行。

这样,非阻塞式挂起 可以确保主线程不会被阻塞,而异步任务仍然能在背后顺利完成,提升程序的响应性。