Rust Async 异步编程(四):Executor

Far planner 代码系列(33) 关于real_world_contour和contour_graph 我们知道far planner前部分的流程是这样子的: 对于特定的地图,如下: 我们要从surround_obs_cloud里抽取生成realworld_contour_,如下图所示: 在这时候,每个node的z坐标都是以机器人的高度定的。如图所示: 先去吃饭啦~待会回来在写 ... 阅读详情

Rust Async 异步编程(四):Executor

异步编程背后到底藏有什么秘密?究竟是哪只幕后之手在操纵这一切?如果你对这些感兴趣,就继续看下去,否则可以直接跳过,因为本章节的内容对于一个 API 工程师并没有太多帮助。

但是如果你希望能深入理解 Rust 的 async/.await 代码是如何工作、理解运行时和性能,甚至未来想要构建自己的 async 运行时或相关工具,那么本章节终究不会辜负于你。

执行器

Rust 的 Future 是惰性的:只有屁股上拍一拍,它才会努力动一动。其中一个推动它的方式就是在 async 函数中使用 .await 来调用另一个 async 函数,但是这个只能解决 async 内部的问题,那么这些最外层的 async 函数,谁来推动它们运行呢?答案就是我们之前多次提到的执行器 executor。

执行器会管理一批 Future (最外层的 async 函数),然后通过不停地 poll 推动它们直到完成。 最开始,执行器会先 poll 一次 Future,后面就不会主动去 poll 了,而是等待 Future 通过调用 wake 函数来通知它可以继续,它才会继续去 poll。这种 wake 通知然后 poll 的方式会不断重复,直到 Future 完成。

构建执行器

下面我们将实现一个简单的执行器,它可以同时并发运行多个 Future。

例子中,需要用到 futures 包的 ArcWake trait,它可以提供一个方便的途径去构建一个 Waker。编辑 Cargo.toml ,添加下面依赖:

[dependencies]
futures = "0.3"

在之前的内容中,我们在 src/lib.rs 中创建了定时器 Future ,现在在 src/main.rs 中来创建程序的主体内容,开始之前,先引入所需的包:

use {
    futures::{
        future::{BoxFuture, FutureExt},
        task::{waker_ref, ArcWake},
    },
    std::{
        future::Future,
        sync::mpsc::{sync_channel, Receiver, SyncSender},
        sync::{Arc, Mutex},
        task::{Context, Poll},
        time::Duration,
    },
    // 引入之前实现的定时器模块
    timer_future::TimerFuture,
};

执行器需要从一个消息通道(channel)中拉取事件,然后运行它们。当一个任务准备好后(可以继续执行),它会将自己放入消息通道中,然后等待执行器 poll。

/// 任务执行器,负责从通道中接收任务然后执行
struct Executor {
    ready_queue: Receiver<Arc<Task>>,
}

/// Spawner 负责创建新的 Future 然后将它发送到任务通道中
#[derive(Clone)]
struct Spawner {
    task_sender: SyncSender<Arc<Task>>,
}

/// 一个 Future,它可以调度自己(将自己放入任务通道中),然后等待执行器去 poll
struct Task {
    /// 进行中的 Future,在未来的某个时间点会被完成
    /// 
    /// 按理来说 Mutex 在这里是多余的,因为我们只有一个线程来执行任务。但是由于
    /// Rust 并不聪明,它无法知道 Future 只会在一个线程内被修改,并不会被跨线程修改。
    /// 因此我们需要使用 Mutex 来满足这个笨笨的编译器对线程安全的执着。
    ///
    /// 如果是生产级的执行器实现,不会使用 Mutex,因为会带来性能上的开销,取而代之的是使用 UnsafeCell
    future: Mutex<Option<BoxFuture<'static, ()>>>,

    /// 可以将该任务自身放回到任务通道中,等待执行器的 poll
    task_sender: SyncSender<Arc<Task>>,
}

fn new_executor_and_spawner() -> (Executor, Spawner) {
    // 任务通道允许的最大缓冲数(任务队列的最大长度)
    // 当前的实现仅仅是为了简单,在实际的执行中,并不会这么使用
    const MAX_QUEUED_TASKS: usize = 10_000;
    let (task_sender, ready_queue) = sync_channel(MAX_QUEUED_TASKS);
    (Executor { ready_queue }, Spawner { task_sender })
}

下面再来添加一个方法用于生成 Future,然后将它放入任务通道中:

impl Spawner {
    fn spawn(&self, future: impl Future<Output = ()> + 'static + Send) {
        let future = future.boxed();
        let task = Arc::new(Task {
            future: Mutex::new(Some(future)),
            task_sender: self.task_sender.clone(),
        });
        self.task_sender.send(task).expect("任务队列已满");
    }
}

在执行器 poll 一个 Future 之前,首先需要调用 wake 方法进行唤醒,然后再由 Waker 负责调度该任务并将其放入任务通道中。创建 Waker 的最简单的方式就是实现 ArcWake 特征,先来为我们的任务实现 ArcWake trait,这样它们就能被转变成 Waker 然后被唤醒:

impl ArcWake for Task {
    fn wake_by_ref(arc_self: &Arc<Self>) {
        // 通过发送任务到任务管道的方式来实现 wake,这样 wake 后,任务就能被执行器 poll
        let cloned = arc_self.clone();
        arc_self
            .task_sender
            .send(cloned)
            .expect("任务队列已满");
    }
}

当任务实现了 ArcWake trait 后,它就变成了 Waker ,在调用 wake() 对其唤醒后会将任务复制一份所有权(Arc),然后将其发送到任务通道中。最后我们的执行器将从通道中获取任务,然后进行 poll 执行:

impl Executor {
    fn run(&self) {
        while let Ok(task) = self.ready_queue.recv() {
            // 获取一个 future,若它还没有完成,则对它进行一次 poll 并尝试完成它
            let mut future_slot = task.future.lock().unwrap();
            if let Some(mut future) = future_slot.take() {
                // 基于任务自身创建一个 LocalWaker
                let waker = waker_ref(&task);
                let context = &mut Context::from_waker(&*waker);
                // BoxFuture<T> 是 Pin<Box<dyn Future<Output = T> + Send + 'static>> 的类型别名
                // 通过调用 as_mut 方法,可以将上面的类型转换成 Pin<&mut dyn Future + Send + 'static>
                if future.as_mut().poll(context).is_pending() {
                    // Future 还没执行完,因此将它放回任务中,等待下次被 poll
                    *future_slot = Some(future);
                }
            }
        }
    }
}

恭喜!我们终于拥有了自己的执行器,下面再来写一段代码使用该执行器去运行之前的定时器 Future:

fn main() {
    let (executor, spawner) = new_executor_and_spawner();

    // 生成一个任务
    spawner.spawn(async {
        println!("howdy!");
        // 创建定时器 Future,并等待它完成
        TimerFuture::new(Duration::new(2, 0)).await;
        println!("done!");
    });

    // drop 掉任务,这样执行器就知道任务已经完成,不会再有新的任务进来
    drop(spawner);

    // 运行执行器直到任务队列为空
    // 任务运行后,会先打印 howdy!, 暂停 2 秒,接着打印 done!
    executor.run();
}

图解程序执行流程

  1. 调用 new_executor_and_spawner() 函数,创建 spawner 和 executor。

  2. 调用 spawner.spawn() 函数,生成一个任务,发送到通道中。

在这里插入图片描述

  1. 调用 executor.run() 函数,取出通道中的任务,调用 poll() 方法开始执行 Future。

在这里插入图片描述

  1. Future 是一个 async 块,TimerFuture 创建后,执行它的 poll() 方法。

在这里插入图片描述

  1. 前一步的 poll() 方法返回 Poll::Pending,executor 将 Future 放回任务中,等待下次被 poll。

在这里插入图片描述

  1. 在 TimerFuture 创建的新线程中,先休眠 2 秒,将 shared_state.completed 设为 true,获取 waker,调用 wake() 方法进行唤醒,于是 Task 的 wake_by_ref() 方法被调用(ArcWake trait),把 Task 放回通道中。

在这里插入图片描述

  1. executor.run() 函数再次取出通道中的任务,调用 poll() 方法开始执行 Future。

  2. Future 是一个 async 块,再次执行它的 poll() 方法,这次返回 Poll::Ready,说明 Future 执行完毕。

  3. executor.run() 函数发现通道内没有任务了,程序退出。

在这里插入图片描述

参考

  1. https://github.com/rustcn-org/async-book
  2. https://www.bilibili.com/video/BV1Ki4y1C7gj
用 Custom Entity 打造 RAP Value Help:从能跑到好用的深度踩坑笔记 本文介绍了在RAP项目中实现Value Help(Fiori输入帮助)的完整流程。通过Custom Entity方式,开发者可以灵活构建不依赖数据库实体的值帮助功能,但需手动实现CDS原本提供的过滤、排序、分页等能力。文章以一个典型场景为例,详细说明了如何为Partner维护界面的City字段创建可搜索的值帮助,包括创建查询实现类、绑定CDS Custom Entity、服务发布等关键步骤,并特别指出了处理排序分页请求等常见问题。最后强调在ABAP Cloud环境下需要自行实现查询能力,而非依赖传统工具类。 阅读详情

相关推荐

STM32与SD卡SPI接口硬件设计:从原理图到调试的完整指南

SPI(串行外设接口)作为一种广泛应用的同步串行通信协议,以其接线简单、驱动成熟的特点,成为连接微控制器与各类外设的经典方案。其工作原理基于主从架构,通过时钟、数据线和片选信号实现全双工通信,在嵌入式系统中具有极高的技术价值,常用于连接传感器、存储设备和显示模块等。在数据存储场景中,SD卡凭借其大容量和便携性成为首选,而通过SPI模式驱动SD卡,则平衡了性能与设计复杂度。本文聚焦于使用Altium Designer进行硬件设计的工程实践,详细解析了STM32微控制器与SD卡通过SPI通信时,电源管理、上拉电

weixin_34101784的博客 555

Rust高级之异步async/await讲解

异步编程是一种在Rust中处理非阻塞操作的方式,允许程序在执行长时间的 I/O 操作时不被阻塞,而是在等待的同时可以执行其他任务。Rust 提供了多种工具和库来实现异步编程,包括async和await关键字、futures和异步运行时(如 tokio、async-std 等),以及其他辅助工具。FutureFuture是 Rust 中表示异步操作的抽象。它是一个可能还没有完成的计算,将来某个时刻会返回一个值或一个错误。async关键字用于定义一个异步函数,它返回一个Future。await。

上善若泪 240

Basilisk vs. STK:开源航天仿真工具在Windows上的实战体验与生态对比

本文对比了开源航天仿真工具Basilisk与商业软件STK在Windows平台上的实战体验与生态差异。从技术架构、安装配置、使用成本、功能覆盖、社区生态到适用场景,全面分析了二者的优劣势,为航天工程师和研究人员提供选型参考。Basilisk在性能和扩展性上表现突出,而STK在可视化与专业模块上更具优势。

weixin_30632089的博客 589

Rust异步编程之async与.await

介绍了Rust中异步编程基本语法async与.await和Future,以及常见的运行时

飞鹰技术 1098

4.7W防削顶单声道D类音频功率放大器HT6872介绍

HT6872是一款低EMI,防削顶失真,单声道免滤波D类音频功率放大器。在6.5V电源,10%THD+N,4Ω负载条件下,输出4.71W功率,在各类音频终端应用中维持高效率并提供AB类放大器的性能。

weichen_78的博客 4703

CP AUTOSAR标准之AUTOSAR OS(AUTOSAR_SWS_OS)(二)

AUTOSAR OS是一个Operating System,即操作系统。它最核心的功能是管理任务和事件的调度,不同任务间的同步流,并且提供监控和错误处理功能。或者用最简化理解就是一个函数调度器。

CoolBreeze的博客 654

Rust异步编程入门与实战:从理论到实践

在开始之前,我想先聊聊什么是异步编程。异步编程是一种编程范式,它允许程序在等待某些操作(如网络请求、文件I/O等)完成时,继续执行其他任务,而不是阻塞等待。传统的同步编程模型中,当我们执行一个耗时操作时,程序会一直等待这个操作完成,然后才继续执行下一步。这种方式在处理并发任务时效率较低,因为CPU在等待I/O操作时处于空闲状态。而异步编程模型中,当我们执行一个耗时操作时,程序会注册一个回调函数,然后继续执行其他任务。当耗时操作完成时,系统会通知程序,然后执行相应的回调函数。

no1coder的博客 1万+

科研绘图系列:R语言circos图(circos plot)

科研绘图系列:R语言circos图(circos plot)

专注生信领域 3435

Rust异步编程深度解析:从Future到Runtime

异步编程已经成为现代高性能应用开发的核心能力。无论是需要同时处理成千上万个连接的 Web 服务器,还是执行大量并发 I/O 操作的数据处理系统,异步模型都能显著提升系统的吞吐量与资源利用率。与 Go 语言的 goroutine 或 JavaScript 的 Promise 不同,Rust 的异步模型建立在零成本抽象的原则之上,通过编译期优化实现了既安全又高效的异步执行。 然而,这种设计也带来了一定的学习曲线 —— async/await 关键字背后隐藏着复杂的状态机转换,Future trait 的工作机制

Allen- 5665

解决:请求的资源[/xxx/]不可用 描述 源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示。

解决:请求的资源[/xxx/]不可用 描述 源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示。

念兮为美 1万+

一句话看懂 vibing-steampunk

摘要: vibing-steampunk(vsp)是一个连接SAP ABAP开发工具(ADT)与AI编码代理(如Claude、GitHub Copilot)的桥梁,通过MCP协议实现自动化代码读写、调试和部署。它利用ADT API直接访问SAP系统,绕过传统GUI操作,支持ECC和S/4HANA等环境。工具提供包分析、依赖检查、LUW行为识别等功能,显著提升ABAP项目维护效率。其创新Hyperfocused模式和方法级编辑大幅降低token消耗,而上下文压缩技术则优化了AI对ABAP代码的理解能力,特别适

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 114

Rust异步编程深度解析

使用async关键字定义异步函数,异步函数返回一个Future} else {// 通知executor再次轮询println!Rust的异步编程是一种强大的编程范式,它可以帮助我们更高效地处理I/O操作,提高程序的性能和响应速度。通过语法和Futuretrait,我们可以编写清晰、简洁的异步代码。作为一名从后端开发转向Rust的开发者,我发现Rust的异步编程与Python的异步编程有很多相似之处,但也有一些不同。

no1coder的博客 4856

WPF中使用TreeView封装组合控件TreeView+DataGrid

wpf的功能非常强大,很多控件都是原生的,但是要使用TreeView+DataGrid的组合,就需要我们自己去封装实现。功能如上图所示, 目前基本上把常用的样式都实现了,购买源码后,可以自行修改样式。本文源码效果如下,(搞了好几天,的确有难度,所以源码也收费,便宜,赚点辛苦费)第一种:技术的选择是TreeView(也就是本文的演示)。这2个图都是第三方控件自带的,并且都是收费使用。首先说明一下,实现上面的效果,有3种方法。本文演示的是使用TreeView的实现。第二种:技术的选择是DataGrid。

故里2130 1651

comprehensive-rust 课程解读:Rust Async 异步编程基础——Futures、async/await、任务与运行时

> 本文基于 Google Android 团队维护的 Rust 培训课程 [comprehensive-rust](https://link.gitcode.com/i/0a832b9543e51ae71de28fbd00aa78f4) 中 Concurrency(并发)模块的 "Async Basics" 章节撰写。该章节的入口文档为 [src/concurrency/async.md](ht

gitblog_00497的博客 410

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Comprehensive Rust 教程:Rust 异步编程的四大陷阱(阻塞执行器、Pin、Async Traits 与取消安全)

异步编程(`async`/`await`)为并发编程提供了便捷而高效的抽象,但 Rust 的 async/await 模型也自带一系列陷阱与"脚枪"(footguns)。本指南以 Google Android 团队维护的 Rust 课程 [Comprehensive Rust](https://link.gitcode.com/i/6d746f3ecc4b96f11f761b8c065c3dde)

gitblog_00915的博客 341

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Rust: 实现 async/await的详细代码【来源脚本之家】

实际上,Rust 选择将 executor 的实现交给第三方,自己只保留相关的交互接口(我在隔壁C++看了看,似乎也是一样的做法,并没有一个官方的 executor 实现,我唯一所知的在语言层面提供支持的只有Golang 的 goroutine)。值得注意的是,在第一种情况下,所有的 Future 都 poll 了一遍,FuturesUnordered 会调用一次 wake,告诉 executor FuturesUnordered 已经运行了一个轮回,wake 具体的实现则取决于 executor。

代码过客 845

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Rust 异步编程速查手册:async-book 总结与参考卡深度解析

本手册以 RustTraining 仓库中 [async-book](https://link.gitcode.com/i/b3d4eafc5123b1c3fb3a7ec36f97a54b) 的收尾章节《Summary and Reference Card》为骨架,将全套异步学习材料浓缩成一份可直接查阅、可复制运行的技术速查卡:从 `async fn → Future` 的心智模型,到 `join

gitblog_00265的博客 1100

async-executor 项目教程

async-executor 项目教程 项目介绍 async-executor 是一个 Rust 语言的异步执行器库,由 smol-rs 组织维护。该库提供了两个参考执行器,它们在性能和功能之间进行了权衡。这些执行器被设计为“足够好”,适用于大多数使用场景。对于更专业的使用场景,建议在 async-task 之上编写自己的执行器。 项目快速启动 安装依赖 首先,确保你已经安装了 Rust 和 Ca...

gitblog_00509的博客 375

Rust异步编程性能优化10个技巧:async-book实战经验

Rust异步编程(async/await)通过高效的任务调度和非阻塞I/O,为构建高性能应用提供了强大支持。本文基于async-book项目的实战经验,总结10个实用性能优化技巧,帮助开发者充分发挥Rust异步编程的潜力,打造响应更快、资源利用率更高的应用程序。 ## 1. 合理选择并发模型:spawn vs join Rust异步编程提供了多种并发组合方式,选择合适的模型对性能至关重要。`f

gitblog_00507的博客 501

Rust 异步编程(二)

首先,汇编语言并不特别便携,因为它是我们可以写入 CPU 的最低级别的人类可读指令,我们编写的汇编指令会因架构而异。由于我们将只编写针对 x86-64 架构的汇编,因此我们只需要学习这个特定架构的几个指令。AT&T 语法和Intel 语法。Intel 语法是编写 Rust 中内联汇编的标准,但在 Rust 中,我们可以指定我们想要使用 AT&T 语法,如果我们想的话。Rust 对内联汇编有自己的看法,对于习惯于 C 中内联汇编的人来说,一开始看起来可能很陌生。

龙哥盟 1212

RustTraining 异步编程开篇:为什么 Rust 的 async/await 与其他语言截然不同

> **导读**:本文基于 RustTraining 仓库中的 [async-book](https://link.gitcode.com/i/0bf887ca0e9f7eafbea40390cc1ed29d) 第一章,系统讲解 Rust 异步模型最根本的差异——**Rust 没有内置异步运行时**,`async fn` 只是把函数编译成实现 `Future` trait 的状态机,需要由第三方执

gitblog_00094的博客 449

Rust 异步运行时原理:从 Future 到 Executor 的完整剖析

Rust异步编程深度解析:从Future到Tokio 本文系统剖析了Rust异步编程的核心机制。主要内容包括: Future trait基础:解析轮询驱动模型、async/await语法糖编译为状态机的原理 核心机制:Pin解决自引用安全问题,Waker实现高效任务通知 运行时实现:从零构建简易Executor,添加定时器支持 Tokio架构:工作窃取调度器设计及其性能优化技巧 性能对比:异步模型相比同步和线程模型的显著优势 最佳实践:避免常见陷阱,正确处理CPU密集型任务和错误 通过丰富代码示例和可视化图

weixin_43862642的博客 755

探索高效异步执行:async-executor 开源项目推荐

探索高效异步执行:async-executor 开源项目推荐 在现代软件开发中,异步编程已成为提升系统性能和响应能力的关键技术。今天,我们将深入介绍一个强大的 Rust 异步执行器库——async-executor,它为开发者提供了一种简单而高效的方式来管理和执行异步任务。 项目介绍 async-executor 是一个 Rust 库,旨在为开发者提供一个轻量级且功能丰富的异步执行器。该项目的目标...

gitblog_00093的博客 573

Rust Async: async-task源码分析

本文转载自知乎地址:https://zhuanlan.zhihu.com/p/92679351?utm_source=wechat_session&utm_med...

Rust语言学习交流 1954

从入门到实践:Rust 异步编程完全指南

在高并发、IO 密集型场景中,异步编程已成为提升程序吞吐量的核心手段。与其他语言的异步实现不同,Rust 异步编程以零成本抽象为核心设计理念,结合其所有权与生命周期机制,实现了高性能与内存安全的兼顾。本文将从基础概念出发,逐步深入 Rust 异步的底层原理、实操技巧,并结合主流框架 Tokio 给出实战案例。

github_28443763的博客 642

Comprehensive Rust 异步运行时指南:Executor 与 Reactor 的职责、选型与实战

Rust 的异步编程模型与大多数语言不同:语言本身并不内置任何异步运行时,`async`/`await` 只是一套语法与类型系统约定,真正让 Future 运转起来的"动力源"需要由运行时(runtime)提供。本文以 Google Android 团队维护的《Comprehensive Rust》课程中 [Runtimes](https://link.gitcode.com/i/b3144fb1

gitblog_00492的博客 857
上一篇: Rust Async 异步编程(三):使用 Waker 来唤醒任务
下一篇: Rust Async 异步编程(五):async/.await
UestcXiye
博客等级 码龄8年 9031粉丝 1306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值