【投稿】刀哥:Rust学习笔记 5

2024-06-23 00:08
文章标签 rust 学习 笔记 投稿 刀哥

本文主要是介绍【投稿】刀哥:Rust学习笔记 5,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

@[TOC](Rust 学习心得<5>:异步代码的几种写法)

Rust历史不长,仍然处于快速发展的历程中。关于异步编程的模式,现在已经发展到async/await协程的高级阶段。大概是因为async/await出现的时间还不长,所以现有大多数的开源项目并不是或不是纯粹使用async/await来书写的,而是前前后后有多种的写法。这样的状况给Rust的学习带来了一些的难度。在这里,我们来捋一捋异步代码的几种写法。

mio

最原始的方式是使用mio进行开发。mio是一个底层异步I/O库,提供非阻塞方式的API,具有很高的性能。实际上mio是对于操作系统epoll/kqueue/IOCP的封装。在C/C++中我们使用libevent之类的库,mio可以理解为对应的Rust版本。基于mio的代码大致如下:

 loop {// Poll Mio for events, blocking until we get an event.poll.poll(&mut events, None)?;// Process each event.for event in events.iter() {if event.is_writable() {// socket可写,开始发送数据}if event.is_readable() {// socket可读,开始接收数据}// socket 关闭,退出循环return Ok(());}}

总的来说,这是完全基于异步事件通知的写法,和C/C++区别不是很大,异步代码对于程序员是一个挑战,当代码逻辑越来越复杂,添加新功能或是解决已有问题的难度也越来越大。

另外,mio实现的是一个单线程事件循环,虽然可以处理成千上万路的I/O操作,但没有多线程的能力,需要自己扩充。

Future Poll

为了更好地规范异步的逻辑,Rust抽象出Future表示尚未发生的事物。这些Future可以用很多方式組合成一个更复杂的复合Future来代表一系列的事件。Future需要程序主动去poll(轮询)才能获取到最终的结果,每一次轮询的结果可能是Ready或者Pending

运行库提供ExecutorReactor来执行Future,也就是调用Futurepoll方法循环执行一系列就绪的Future,当Future返回Pending的时候,会将Future转移到Reactor上等待唤醒。Reactor被用来负责唤醒之前无法完成的Future。事实上,tokioReactor是基于mio实现的,而async-std/smol则是封装了epoll/kqueue/IOCP,提供类似的功能。

手动实现Future是一件相对繁琐的工作,主要的问题在于异步模式本身的特性。例如,接收网络数据,无法臆测每次轮询会收到多少字节的数据,往往需要开辟一段接收缓冲区容纳数据,协议解码也需要一个状态机拼包向上层提交;发送网络数据存在相似问题,发送数据时底层未就绪,则缓冲发送数据,待下次轮询时,需要首先检查并处理发送缓冲区。另外还有一些值得注意的地方,如果手动实现的Future返回Pending,则必须自己实现唤醒机制,也就是需要将cx克隆一份记下来,然后在适当的时侯调用cx.wake()。因为网络相关的功能往往是分层的,因此手动的Poll循环也会是层层堆叠的,这时候,返回值Poll::Ready(T)就有学问了。泛型T可能包裹各种不同的数据,Option<T>Result<T,E>,或者两者的组合。因为最外层还有一个Poll<T>,所有这时候的match语句写起来会非常臃肿,粘贴复制写很多代码,完成的功能却非常有限,而且由于这些代码很相似,大大增加了出错的可能性。

标准库中仅仅定义了Future,更多的相关功能需要引用futures-rs类库,里面定义了一系列有关异步的操作,包括StreamSinkAsyncReadAsyncWrite等基础Trait,以及对应实现了大量方便操作的组合子的Ext Trait,特别用途的fusedBoxTry系列的扩展,诸如join!select!pin_mut!等一系列的宏。理论上,不使用这些扩展也能写出代码,只不过那样的代码很可能篇幅会长的可怕。值得一提的是,除了一些可以简化代码的过程宏之外,扩展Trait提供的组合子也会让代码精简不少。比如Future::and_then可以让代码写成链式调用的方式;Sink::send包装了Sink发送三步骤 poll_ready/start_send/poll_flush,使用.await一行代码直接就可以完成发送。因此,很多poll方式的代码实际上是准确地说是混合式的,其中也使用了不少async代码块。

总之,搞清楚Future相关的这些内容是需要花费不少时间,更不用说用它们来写代码了。不过,即便是使用async/await这种更高级原语,也是有必要了解底层的工作原理和实现机制,所谓知其然知其所以然。

async/await

使用async/await可以将异步的代码写得类似同步的过程,更加符合人体工程学。因为async被翻译为一个Future状态机,原先在poll方式中需要处理的与Pending相关的状态现在都由async生成的状态机自动完成,因此大大减轻了程序员的心智负担。

如前所述,底层的Futures提供了很多方便的组合子扩展Future,使用起来很简洁,可以极大地简化代码。例如,上文提到过的Sink::send包装了发送缓冲区的实现和异步发送的三个步骤;AsyncRead::read_exact实现了读取指定字节数的功能,在处理网络协议解析时可以避免手写一个拼包状态机;AsyncWrite::write_all实现了发送全部数据以及发送缓冲,等等。正是在这些底层功能的支持下,async/await成为了更高级的书写异步代码的方式。也许会有少许担心,这样所谓“高级”会不会在性能上有很大损失?笔者个人不这么认为。自动实现的状态机也许未必比程序员手动完成的性能更差。状态机编程对于任何人,即便是一个有经验的程序员都是不小挑战。蹩脚的状态机实现不仅可能有性能问题,更大的风险来自于实现上的漏洞,以及维护上的困难。代码写出来更多是给别人看的,完成同样的功能,简洁的代码更有可能是更高质量的代码。

以下例子是固定长度分割的报文接收过程,使用async/await是很简单的。如果实现为一个Stream/poll_next,代码会复杂很多。

    /// convenient method for reading a whole framepub async fn recv_frame(&mut self) -> io::Result<Vec<u8>> {let mut len = [0; 4];let _ = self.inner.read_exact(&mut len).await?;  // inner socket, 支持 AsyncReadlet n = u32::from_be_bytes(len) as usize;if n > self.max_frame_len {let msg = format!("data length {} exceeds allowed maximum {}",n, self.max_frame_len);return Err(io::Error::new(io::ErrorKind::PermissionDenied, msg));}let mut frame = vec![0; n];self.inner.read_exact(&mut frame).await?;Ok(frame)}

最后,完全使用async/await写代码目前还有几个问题:

async trait

当前Trait 不支持 async fn,无法直接用Trait来抽象异步方法。暂时解决办法是使用三方库 async-trait。如下:

use async_trait::async_trait;#[async_trait]
trait Advertisement {async fn run(&self);
}

async_trait将代码转换为一个返回 Pin<Box<dyn Future + Send + 'async>> 的同步方法。因为装箱和动态派发的原因,性能上会有少许损失。

异步析构

当前drop方法必须是同步调用,不能使用await语法。当一个I/O对象越过生命周期被析构,往往在关闭底层句柄之前,还需要完成某些I/O操作。比如,通知网络对端连接已经关闭。在同步代码中,我们只需要在drop()中置入这些操作,但是在异步代码中,无法在drop()中做类似的事情。

解决办法,总是在异步I/O对象越过生命周期之前显式地执行关闭动作,或是,实现一个类似GC的功能,专门负责清理工作。

展望

笔者在学习Rust过程中,主要关注网络相关的并发编程。因为之前有在Go版本的ipfs/libp2p上的开发经验,故而学习研究了rust-libp2p以及nervos tentaclerust-libp2pParity实现的准官方版本,但是这个项目的代码及其难懂,过于强调使用泛型参数的抽象,导致代码可读性非常差。请教了代码作者,他承认代码可能有些复杂,但也强调都是有原因的... nervos tentacle的实现在协议上不够完整,特别是与标准libp2p并不兼容。两个项目共有的特点是主要用poll的方式写代码,逻辑上都是状态机的嵌套。

因此,笔者试图完全使用async/await方式重构libp2p,参考rust-libp2p的实现,代码协程化,向上层提供纯粹的异步接口,争取在API层面的体验接近go-libp2p,这是推广Rust协程机制的一个尝试,同时也是个人的一个学习的过程。目前刚刚起步,仅完成了secioyamux部分,待合适时机开源,期望更多Rust爱好者共同来开发完善。

参考:Asynchronous Destructors

这篇关于【投稿】刀哥:Rust学习笔记 5的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1085795

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件