您的位置:首页 > 大数据 > 人工智能

【译】Async/Await(五)—— Executors and Wakers

2021-02-08 13:27 826 查看

原文标题:Async/Await
原文链接:https://os.phil-opp.com/async-await/#multitasking
公众号: Rust 碎碎念
翻译 by: Praying

Executors and Wakers

使用 async/await,可以让我们以一种全完异步的方式来与 future 进行更为自然地协作。然而,正如我们之前所了解到的,future 在被轮询之前什么事也不会做。这意味着我们必须在某个时间点上调用

poll
,否则异步的代码永远都不会执行。

对于单个的 future,我们总是通过使用一个循环手动地等待每个 future。但这种方式十分地低效,且对于一个创建大量 future 的程序来讲也不适用。针对这个问题的最常见的解决方式是定义一个全局的

executor

Executors(执行器)

executor 的作用在于能够产生 future 作为独立的任务,通常是通过某种

spawn
方法。接着 executor 负责轮询所有的 future 直到它们完成。集中管理所有的 future 的巨大优势在于,只要当一个 future 返回
Poll::Pending
时,executor 就可以切换到另一个 future。因此,异步操作可以并行执行并且 CPU 始终保存繁忙。

许多 executor 的实现充分利用 CPU 多核心的优势,它们创建了一个线程池[1],该线程池能够在工作足够多的情况下充分利用所有的核心,并且使用类似work stealing[2]的方式在核心之间进行负载均衡。还有针对嵌入式系统优化了低延迟和内存负载的特殊的 executor 实现。

为了避免重复轮询 future 的负担,executor 通常会充分利用由 Rust 的 future 支持的 waker API。

Waker

Waker API 背后的设计理念是,一个特定的Waker[3]类型,包装在

Context
类型中,被传递到
poll
的每一次执行。这个
Waker类型
由 executor 创建,并且可以被异步任务用来通知自己的完成。因此,executor 不需要在一个 future 返回
Poll::Pending
之前对其调用
poll
,直到它被对应的 waker 调用。

这可以通过一个小例子来阐述:

async fn write_file() {
    async_write_file("foo.txt", "Hello").await;
}

这个函数异步地把一个字符串“Hello”写入到文件

foo.txt
中。因为硬盘写入需要一点儿时间,所以 future 上的第一次
poll
调用很大可能返回
Poll::Pending
。尽管如此,硬盘驱动将把传递给
poll
调用的
Waker
存储起来,并在当文件被完全写入磁盘后使用它来提醒 executor,通过这种方式,executor 在收到 waker 提醒之前不需要浪费时间一次又一次地去轮询这个 future。

当我们在后面的章节实现自己的支持 waker 的 executor 时,我们就会看到

Waker
类型更详细的工作原理。

协作式多任务?

在本文(系列)开头,我们讨论了抢占式和协作式多任务。抢占式多任务依赖于操作系统在运行中的任务间进行强制切换,协作式多任务则需要任务通过一个

yield
操作自愿放弃对 CPU 的控制权。协作式多任务的巨大优势在于,任务可以自己保存自身状态,从而产生更为高效的上下文切换,并使得在任务间共享相同的调用栈成为可能。

虽然看上去可能不太明显,但是 future 和 async/await 是一种协作式多任务模式的实现:

  • 每个被添加到 executor 的 future 是一个协作式任务。

  • 不同于显式的

    yield
    操作,future 通过返回
    Poll::Pending
    (或者是结束时的
    Poll::Ready
    )来放弃对 CPU 核心的控制权。

    没有什么可以强制让 future 放弃 CPU,future 可以永远不从

    poll
    里面返回,例如,无限循环。

  • 因为每个 future 都能阻塞 executor 中其他 future 的执行,所以我们需要确信它们不是恶意的。

  • Futures 内部存储了需要在下次

    poll
    调用继续执行所需的所有状态。通过 async/await,编译器会自动探测所有需要的变量并将其存储在生成的状态机内部。

      只保存继续执行需要的最小状态
    • 因为
      poll
      方法在返回时放弃了调用栈,所以同一个栈可以被用于轮询其他的 future。

    我们可以看到,future 和 async/await 完美契合协作式多任务模式,它们只是用了一些不同的技术。接下来,我们将会交替使用“任务(task)”和“future”。

    参考资料

    [1]

    线程池: https://en.wikipedia.org/wiki/Thread_pool

    [2]

    work stealing: https://en.wikipedia.org/wiki/Work_stealing

    [3]

    Waker: https://doc.rust-lang.org/nightly/core/task/struct.Waker.html

    ```
  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: