【翻译】从头实现Rust异步执行器

本文介绍如何从头实现一个Rust异步执行器,目标是使用简单安全的代码并保持高性能。文中详细讲解了执行器的接口、任务处理、执行线程、任务执行、 Panic处理和执行器效率等方面,还提到了任务队列和任务窃取的概念,展示了如何创建一个高效且正确的异步执行器实现。
摘要由CSDN通过智能技术生成

原文:https://stjepang.github.io/2020/01/31/build-your-own-executor.html

现在我们已经构建了block_on函数,是时候进一步将其转换为一个真正的执行器了。我们希望我们的遗执行器不只是一次运行一个future,而是同时运行多个future!

这篇博文的灵感来自于 juliex,一个最小的执行器,作者也是Rust中的async/await功能的开拓者之一。今天我们要从头开始写一个更现代、更清晰的juliex版本。

我们的执行器的目标是只使用简单和完全安全的代码,但是性能可以与现有的最佳执行器匹敌。

我们将用作依赖的crate包括 crossbeam、 async-task、 once_cell、 futures 和 num_cpus。

接口

执行器只有一个函数,就是运行一个future:

fn spawn<F, R>(future: F) -> JoinHandle<R>where    F: Future<Output = R> + Send + 'static,    R: Send + 'static,{    todo!()}

返回的JoinHandle是一种实现了Future的类型,在任务完成后可以取得其输出。

注意这个spawn()函数和 std::thread::spawn()之间的相似之处——它们几乎是等价的,除了一个产生异步任务,另一个产生线程。

下面是一个简单的例子,生成一个任务并等待它的输出:

fn main() {    futures::executor::block_on(async {        let handle = spawn(async { 1 + 2 });        assert_eq!(handle.await, 3);    });}

将输出传递给JoinHandle

既然 JoinHandle是一个实现 Future 的类型,那么让我们暂先简单地将它定义为一个固定到堆上的future的别名:

type JoinHandle<R> = Pin<Box<dyn Future<Output = R> + Send>>;

这个方法目前可行,但是不要担心,稍后我们会将它作为一个新的结构清晰地重写,并手动实现 Future。

产生的 future 的输出必须以某种方式发送到 JoinHandle。一种方法是创建一个 oneshot 通道,并在future完成时通过该通道发送输出。那么 JoinHandle 就是一个等待来自通道的消息的future:

use futures::channel::oneshot;
fn spawn<F, R>(future: F) -> JoinHandle<R>where    F: Future<Output = R> + Send + 'static,    R: Send + 'static,{    let (s, r) = oneshot::channel();    let future = async move {        let _ = s.send(future.await);    };
    todo!()
    Box::pin(async { r.await.unwrap() })}

下一步是在堆上分配future包装器,并将其推入某种全局任务队列,以便由执行程序处理。我们称这种分配的future为一项任务。

任务的剖析

任务(task)包括future和它的状态。我们需要跟踪状态,以了解任务是否计划运行、是否当前正在运行、是否已经完成等等。

下面是我们的Task类型的定义:

struct Task {    state: AtomicUsize,    future: Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>,}

我们还没有确定状态到底是什么,但它将是某种可以从任何线程更新的 AtomicUsize。我们以后再说吧。

Future 的输出类型是()ーー这是因为 spawn ()函数将原始的 future 包装成一个将输出发送到 oneshot 通道,然后简单地返回()。

future被固定在堆上。这是因为只有pin的future才能被轮询(poll)。但是为什么它还被包装在Mutex中呢?

每个与任务相关联的 Waker 都会保存一个 Task 引用,这样它就可以通过将任务推入全局任务队列来唤醒任务。问题就在这里: 任务实例在线程之间共享,但是轮询future需要对它的可变访问。解决方

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值