Rust异步编程简介

Rust异步编程简介

计算机已经尽可能快了。加快程序速度的一种方法是并行或并发执行操作。这两个术语之间有细微的区别。并行执行意味着我们同时在两个不同的 CPU 上执行两个不同的任务。并发执行意味着单个 CPU 通过交错执行这些任务,同时在多个任务上取得进展。

在这里插入图片描述

Rust 标准库为底层操作系统提供了绑定和抽象。这包括线程,一种并行运行代码的方式。并行性由操作系统管理,您可以拥有与CPU核心一样多的线程,但也可以有更多,并且操作系统决定何时执行什么。这可能非常繁重并且有大量开销。

因此,我们陷入了两种方法:要么按顺序运行所有内容,要么使用操作系统线程并行执行,这可能会导致开销。对于某些领域(例如 Web 或网络应用程序 ,即IO密集型)来说,它们都可能不是最佳解决方案。

异步试图解决这些问题。异步是一种顺序编写代码但同时并发执行代码的方法,无需管理任何线程或执行。这个想法是 将现有代码分割成任务,然后执行一部分代码,并让异步运行时选择下一个需要执行的任务。然后,运行时决定何时执行什么,并且可以以非常有效的方式执行

它还利用了这样一个事实:大多数时候,CPU 正在等待某些事情发生,例如网络请求或要读取的文件。看下面的代码行。

let mut socket = net::TcpStream::connect((host, port)).unwrap();

我们所做的就是建立一个 TCP 连接。但这需要时间。对于您来说不一定引人注目,但对于计算机来说,这意味着什么都不做,只是等待建立连接。 其实 我们可以更好地利用这段时间。

异步原语

并发执行在编程领域并不是什么新鲜事。此外,异步编程已经存在了一段时间,您可能在 JavaScript 或 C# 中看到过类似的东西。但在 Rust 中,乍一看事情可能很相似,但如果我们仔细观察就会有所不同。

一个很大的区别是 Rust 没有异步运行时。我们需要一个异步运行时来管理任务的正确执行,但参与的 Rust 团队认为不存在“一刀切”的异步运行时,开发人员应该有权选择适合自己需求的运行时。从概念上讲,这不同于例如Go,它只有一种并发模型:goroutines。开发人员却陷入困境。

在 Rust 中,我们可以决定使用哪一种。尽管如此,Rust 为我们提供了一种为异步执行器准备任务的方法。这是通过使用 Future trait 的抽象来完成的。 Future trait是 Rust 异步编程的核心。它是一种trait,代表一种尚不可用但在未来某个时候可用的值。这与 JavaScript 中的 Promise 非常相似。

实现 Future 的所有内容都可以在异步运行时中执行。 Future trait定义如下:

pub trait Future {
    type Output;
    // Required method
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

这很简单。它有一个关联类型 Output ,它代表了未来的值。它有一个名为 poll 的方法,它带有 Context 并返回 Poll

Poll 是一个具有两种状态的枚举。要么 Pending ,这意味着我们等待一个值。或者 Ready ,表示该值可用。 Ready 变体保存 Output 类型的输出。

pub enum Poll<T> {
    Ready(T),
    Pending,
}

Context 目前仅用于提供对 Waker 对象的访问。 Waker 是告诉运行时再次轮询此任务所必需的。

好吧好吧,那是什么?Polling,waking?让我们深入挖掘一下。

执行

如前所述, Future trait用于抽象可以在异步运行时中执行的任务。但这是如何运作的呢?

公平地说,详细来说,这取决于所使用的异步运行时,但一些基本概念对于所有这些都是相同的。

尼克·卡梅伦 (Nick Cameron) 撰写了有关此主题的概述,总结如下:

异步运行时有一个执行器。执行器通常有两个关键 API: spawnblock_onblock_on 用于 阻塞等待当前线程上的任务完成。 spawn 用于在执行器上启动一个新任务,但非阻塞。它立即返回。返回值取决于 Future 。是否有异步发生?然后轮询 Future 将立即返回 Poll::Pending ,同时还为执行器设置规则,以便在任务准备好时唤醒任务。这可以是操作系统上的一些 IO 事件,例如已建立的 TCP 连接。如果没有任何异步发生, Future 将返回 Poll::Ready 及其返回值。

一旦事件发生,waker 指示执行器再次轮询相同的future,可能已经有结果了。

语法糖: async and await

好的,所以您需要的只是实现 Future 的函数或结构,然后您就完成了。这可行吗?嗯,从字面上看,这并不那么容易。实现 Future trait可能非常艰巨,而且不太符合工效学。

这就是 Rust 引入 asyncawait 关键字的原因。要使某些内容异步,它需要返回 Future 。因此,如果您想要一个方法 read_to_string ,这是同步版本:

fn read_to_string(&mut self, buf: &mut String) -> Result<usize>;

异步版本如下所示:

fn read_to_string(&mut self, buf: &mut String) -> impl Future<Output = Result<usize>>;

这儿有个有语法糖。您可以将其声明为 async ,而不是返回 Future

async fn read_to_string(&mut self, buf: &mut String) -> Result<usize>;

您也不需要自己进行poll。您可以使用 await 关键字等待 Future 的结果。

let result = fileread_to_string(&mut buf).await;

在幕后,Rust 编译器为您创建了Future。它通过 将代码拆分为多个任务来实现这一点每个 await 都是分隔任务的分割点。然后,编译器会为您创建一个状态机,并为您实现 Future trait 。对于每个 await ,状态机都会被轮询并可能移动到下一个状态。 Tokio 团队在本教程中精彩地展示了how those Futures can be implemented or created by the compiler in this tutorial。

Tokio 是最流行的运行时之一,专为网络应用程序的异步执行而设计。它也是早期异步的playground,并且很稳定,可用于生产,而且很可能也是您正在使用的任何 Web 框架的基础。它不仅提供了操作系统事件的必要抽象,还提供了具有不同模式的功能丰富的运行时,以及标准库 IO 和网络功能的异步表示。如果你想开始使用 Rust 中的异步,Tokio 是一个不错的选择。

Traits 中的async 方法

所有这些都导致了异步Rust中最需要的,也是最期待的特性之一:在trait中定义async 方法。该功能最近已在 Rust 中引入,但仍然存在一些限制。下面的问题是,我们作为开发人员希望使用漂亮的 async / await 语法进行编写,但编译器需要为自动生成的代码准备 Future trait实现状态机,这可能会变得非常复杂

让我们看一个例子,它想要为聊天应用程序定义一个编写接口,名为 ChatSink 。这就是我想写的。

pub trait ChatSink {
  type Item: Clone;
  async fn send_msg(&mut self, msg: Self::Item) -> Result<(), ChatCommErr>;
}

一旦我们想将其转换为使用 Future 实现的东西,事情就会变得有点棘手。我们需要定义一个 Future 返回类型,而不是 async 方法,但我们不知道它会是哪个 Future !这将由trait 的实现者在稍后阶段定义。所以我们能做的就是说无论发生什么,它都会实现 Future trait 。这是通过使用 impl 关键字来完成的。

pub trait ChatSink {
  type Item: Clone;
  fn send_msg(&mut self, msg: Self::Item) -> impl Future<Output = Result<(), ChatCommErr>>;
}

但有趣的是 impl Trait 也只是关联类型的语法糖。事实上,会产生类似这样的东西。

pub trait ChatSink {
  type Item: Clone;
  type $: Future<Output = Result<(), ChatCommErr>>;
  fn send_msg(&mut self, msg: Self::Item) -> Self::$;
}

但这还不是全部,我们遗漏了一个非常重要的细节。与其他 impl Trait 解决方法相比, Future 需要添加生命周期参数。这与 future 内部的处理方式有关:它们不执行代码,它们只是将执行代码的机会传递给另一个运行时环境,即我们之前提到的执行器!异步函数创建这样的 future,并且它们需要保留对 输入参数的所有引用。根据 Rust 的所有权规则,所有这些引用都需要与future本身一样长久。为了确保此信息可用,我们需要向 Future trait添加一个生命周期参数

这就产生了一个称为 通用关联类型的功能。 ChatSink 特征的等效版本如下所示:

pub trait ChatSink {
  type Item: Clone;
  type $<'m>: Future<Output = Result<(), ChatCommErr>> + 'm;
  fn send_msg(&mut self, msg: Self::Item) -> Self::$<'_>;
}

但在 Rust 1.75 之前,这一切都是不可能的。这已经发生了变化,但仍然存在一些限制。 impl Trait 目前不允许添加用户定义的trait 约束(trait bounds),如果您作为开发人员想要实现库中的trait ,则该功能是必需的。更不用说,如果您想决定异步代码只能在单线程或多线程运行时上工作,那么添加 SendSync 标记特征就是您想要的自行定义(更多关于这些标记特征的信息请参见here)。

为什么我需要知道所有这些?

公平地说,这是很多信息,并且深入了解了异步 Rust 的本质细节。但这是有原因的。就像 Rust 中的一切一样,事情一开始看起来简单明了,但一旦你深入挖掘,你会发现有很多复杂性和很多需要考虑的事情

Rust 中的异步编程也会发生同样的情况。您肯定已经完成了定义异步方法。毕竟,您正在阅读 Shuttle 博客,并且异步为 Rust 中的 Web 开发提供了动力。起初,它们很简单,但突然间您可能会看到无法掌握的错误消息

您在异步函数中定义一个资源,将其包装在 std::sync::Mutex 中,并在锁定它后获取它的 MutexGuard 。突然您决定调用异步 API 并传递 .await 点。编译器会对你抱怨,因为 MutexGuard 没有实现 Send 特征,并且你不能将它传递给另一个线程。但为什么需要将它传递给另一个线程呢?您所做的只是调用异步函数?这就是运行时的用武之地。您的运行时配置可能是多线程工作的,并且您永远不知道哪个工作线程执行当前任务。由于您需要为自动 Future 实现准备好所有资源,因此所有这些引用和资源都需要是线程安全的。还有更多的陷阱,但这是另一次的事情了。

进一步阅读

如果您已经了解了这么多,您可能会对以下资源感兴趣:

  • Nick Cameron writes a lot about Async Rust, you might want to check it out.
  • So does Without Boats, who gives a lot of insights into the design and development of async Rust.
  • The Tokio Tutorial on Async in Depth is nothing but excellent.
  • So is the Async chapter in “Programming Rust” by Jim Blandy, Jason Orendorff, and Leonora Tindall.
  • Also check out my talk at the first Shuttle Labs.

Async Rust in a Nutshell

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/563885.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【支付宝】对接手机网站支付踩坑点记录

前言 简单记录一下对接Wap支付的问题&#xff0c;alipay和wxpay认证过程差不多&#xff0c;有个体商户或企业即可&#xff0c;前者文档不易懂后者还好&#xff0c;但是wxpay门槛高&#xff0c;个人认为pc网站支付(native支付)就是为了收300认证费&#xff01; 应用公私钥 第一…

【数据结构】时间复杂度的例题

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;数据结构 &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 这篇文章是关于时间复杂度的一些例题&#xff0c;关于时间复杂度和空间复杂度和算法的计算效率的基本知识点我放在…

应变计技术解读:如何精确测量结构物的形变

振弦式应变计是一种用于精确测量结构物形变的高精度仪器。这种应变计利用了振弦原理&#xff0c;即物体的振动频率会因其尺寸、形状或应变状态的改变而改变。通过测量这种频率变化&#xff0c;振弦式应变计能够提供关于材料形变的详细信息&#xff0c;这在结构健康监测、工程试…

Apache POI报表统计

Apache POl是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用 POl 在 Java 程N序中对Miscrosoft Office各种文件进行读写操作。一般情况下&#xff0c;POI都是用于操作 Excel 文件。 导入Maven坐标&#xff1a; <dependency>&l…

【数据结构(八)上】二叉树经典习题

❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你学更多数据结构的知识 目录 1.前言2.经典习题2.1相同的树2.2另一棵子树2.3翻转二叉树2.4平衡二叉树2.5对…

免费开源圈子社交交友社区系统 可打包小程序 支持二开 源码交付!

线上社交的好处&#xff1a; 当今社会&#xff0c;人们越来越依赖于网络社交。互联网无疑为人类带来了许多好处&#xff0c; 其中一个就是线上社交。通过各种社交平台&#xff0c;人们可以随时随地互动交流&#xff0c;扩大自 己的社交圈&#xff0c;丰富生活。但是&#xf…

智慧水务能效管理系统平台/地下污水厂配电系统电气安全设计

安科瑞电气薛瑶瑶18701709087 1、引言 地下水污厂在城市建设中扮演着重要的角色,负责对城市污水和废物进行处理和排放。然而,由于地下水污厂中存在着许多危险因素,如有害气体、液体和固体废物等,因此要保证电气安全。电气安全系统是地下水污厂安全生产的重要保障措施之一,包括…

常见的软件架构模式

在软件开发过程中&#xff0c;软件架构模式是实现高质量、可扩展系统的关键。本文将介绍一些常见的软件架构模式&#xff0c;分析其优缺点和适用场景&#xff0c;从而帮助大家在实际项目中做出更明智的架构选择&#xff08;注意以下的架构模式相互之间并不一定互斥&#xff0c;…

imx6ull设备树驱动--pinctl、ioctl

添加pinctl节点 进入arch/arm/boot/dts目录下dts文件 在iomuxc下添加pinctlled节点 将 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03&#xff0c;电气属性&#xff08;配置GPIO一些列寄存器&#xff09;值为 0X10B0 添加led设备节点 与上一节一样&#xff0c;在 / 下面添加设备节…

2024年遥感技术与地理信息国际学术会议(ICRSTGI 2024)

2024年遥感技术与地理信息国际学术会议(ICRSTGI 2024) 2024 International Conference on remote sensing technology and geographic information 一、【会议简介】 2024年遥感技术与地理信息国际学术会议&#xff0c;将汇集世界各地的顶级专家和学者。 在这个会议上&#xf…

Springboot+Vue项目-基于Java+MySQL的网上购物商城系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

交通公路气象站:监测公路沿线气象

TH-GQX8交通公路气象站是一种专门用于监测公路沿线气象状况的设备系统。它通常由分布在公路沿线的若干个自动气象站联网组成&#xff0c;主要任务是实时监测和记录多种气象数据&#xff0c;为交通管理部门和驾驶员提供准确的路况信息。这些气象数据包括气温、湿度、风速、风向、…

高速公路交通运输大数据平台解决方案

前言 交通运输行业面临着多重挑战。其管控困难&#xff0c;涉及广泛地理范围&#xff0c;导致监控成本高且难以及时响应&#xff1b;同时&#xff0c;行业内数据量大&#xff0c;地理信息数据繁多&#xff0c;缺乏高效的可视化工具来揭示数据规律并优化业务&#xff1b;货运和…

润石科技(RUNIC)汽车电子应用方案和物料选型

一、润石科技&#xff08;RUNIC&#xff09;简介 江苏润石科技有限公司是一家专注于高性能、高品质模拟/混合信号集成电路研发和销售的高科技半导体设计公司。公司主要产品线分为两类&#xff1a;信号链和电源管理&#xff0c;其中信号链包含运算放大器、比较器、模拟开关、数…

微信小程序开发

微信小程序隶属于前端&#xff0c;因此我们只需要了解掌握一些基本的功能与业务逻辑即可。 HttpClient HttpClient 是Apache Jakarta Common 下的子项目&#xff0c;可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包&#xff0c;并且它支持 HTTP 协议…

APP自动化测试-Android SDK SDK Manager.exe或者uiautomatorviewer.bat打不开,点击就一闪而已的原因

原因是找不到Java.exe的路径&#xff0c; 如果是uiautomatorviewer.bat打不开&#xff0c;则使用文本编辑器打开它&#xff0c;然后添加java安装路径 set java_exeC:\Program Files\Java\jdk1.8.0_321\bin\java.exe 同理&#xff1a; 如果是SDK Manager.exe和AVD Manager.ex…

一个不同长度元素排序找行和列的需求

1、需求&#xff1a;三种长度的元素&#xff0c;分别是4、8、12&#xff0c;每一行的长度是12&#xff0c;超过12就排到下一行&#xff0c;我们将这三种类型的多个元素打乱&#xff0c;然后找到这些元素对应的行和列。 如下图&#xff1a; 2、解决思路&#xff1a; 创建一个长…

Java中的构造器

即使在类中什么都不写也会自动的生成一个构造器 注意 使用new关键字是在调用构造器 如果定义了有参构造 那么就不会默认的走Person person new Person();如果没有自己手动的定义无参构造就不能使用 在idea中 用按键Altinsert可以快速生成有参、无参构造&#xff08;某些品牌的…

SpringBoot:SpringMVC(下)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、RequestBody补充二、PathVariable三.RequestPart:四.ResponseBody五.CookieValue&#xff0c;SessionAttribute&#xff0c;RequestHeader 前言 提示&…

window下qt可执行程序打包

添加软件图标 方法1&#xff1a; QApplication a(argc, argv);// 设置应用程序图标QIcon appIcon("C:/Users/Administrator/Desktop/otp.png"); // 替换为你的图标文件路径a.setWindowIcon(appIcon); 缺点&#xff1a;运行后才显示图标&#xff0c;可执行程序文件图…