当前位置: 首页 > 后端技术 > Python

编写异步代码的几种方法-Rust学习笔记

时间:2023-03-26 15:30:56 Python

作者:谢经纬,人称“刀哥”,20年IT从业者,数据通信网络专家,电信网络架构师,现任Netwarps开发总监。刀哥在操作系统、网络编程、高并发、高吞吐量、高可用等领域有多年的实践经验,对网络和编程方面的新技术有着浓厚的兴趣。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{//PollMioforevents,blockinguntilwegetaevent.poll.poll(&mutevents,None)?;//处理每个事件。foreventinevents.iter(){ifevent.is_writable(){//socket可写,开始发送数据}ifevent.is_readable(){//socket可读,开始接收数据}//socket关闭,退出循环返回确定(());总的来说,这种方法完全基于异步事件通知,与C/C++区别不大。异步代码对程序员来说是一个挑战。当代码逻辑越来越复杂时,增加新功能或解决已有问题的难度也越来越大。此外,mio实现了单线程事件循环。虽然它可以处理上千个I/O操作,但是它不具备多线程的能力,需要自己扩展。FuturePoll为了更好的规范异步逻辑,Rust抽象出Future来表示还没有发生的事情。这些Future可以通过多种方式组合起来,形成一个更复杂的复合Future,代表一系列事件。Future需要程序主动轮询(poll)得到最终的结果。每次投票的结果可能是就绪或待处理。runtime库提供了Executor和Reactor来执行Future,即调用Future的poll方法循环执行一系列就绪的Futures。当Future返回Pending时,它会将Future转移到Reactor中等待它醒来。Reactor用于负责唤醒之前无法完成的Future。其实tokio的Reactor是基于mio实现的,async-std/smol封装了epoll/kqueue/IOCP来提供类似的功能。手动实现Future是一个比较繁琐的工作,主要问题在于异步模式本身的特点。例如,在接收网络数据时,无法猜测每次轮询将接收到多少字节的数据。通常需要开辟一个接收缓冲区来容纳数据。协议解码也需要状态机包提交给上层;发送网络数据也有类似的问题,当发送数据时底层没有准备好,缓存发送数据。下次轮询时,需要先检查并处理发送缓冲区。此外,还有一些值得注意的地方。如果手动实现的Future返回Pending,则必须自己实现唤醒机制,即需要clonecx写下来,然后在合适的时候调用cx.wake()。因为网络相关的功能往往是分层的,所以手动的Poll循环也会一层层叠加。这时候,返回值Poll::Ready(T)就学会了。通用类型T可以包装各种数据、Option、Result或两者的组合。因为最外层有一个Poll,这个时候的match语句写起来会很臃肿,粘贴复制了很多代码,但是完成的功能很有限,而且因为这些代码很类似的,出错的可能性是大大增加的。标准库中只定义了Future,更多相关功能需要参考futures-rs类库,其中定义了一系列的异步操作,包括Stream、Sink、AsyncRead、AsyncWrite等基本Traits,并相应的实现了一大堆一些方便的操作Combinator的ExtTrait,专用的fused,Box,Try系列的扩展,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底层提供了很多方便的combinators来扩展Future,使用起来非常简单,可以大大简化代码。比如上面提到的Sink::send,包装了发送缓冲区的实现和异步发送的三个步骤;AsyncRead::read_exact实现读取指定字节数的功能,在处理网络协议分析时可以避免手写封装状态机;AsyncWrite::write_all实现了发送所有数据和发送buffer等,正是在这些底层函数的支持下,async/await成为了一种更高级的异步代码编写方式。也许会有一点担心,所谓的“高级”会不会在性能上有很大的损失?我个人不这么认为。自动实现的状态机不一定比程序员手动实现的性能差。状态机编程对任何人来说都是一个挑战,即使是有经验的程序员也是如此。蹩脚的状态机实现可能不仅仅存在性能问题,更大的风险来自于实现漏洞和维护困难。代码写的多是为了让别人看。为了完成相同的功能,简洁的代码更有可能是更高质量的代码。下面的例子是一个定长分段的消息接收流程,使用async/await非常简单。如果实现成一个Stream/poll_next,代码会复杂很多。///读取整个帧的便捷方法pubasyncfnrecv_frame(&mutself)->io::Result>{letmutlen=[0;4];让_=self.inner.read_exact(&mutlen).await?;//内部套接字,支持AsyncReadletn=u32::from_be_bytes(len)asusize;ifn>self.max_frame_len{letmsg=format!("datalength{}exceedsallowedmaximum{}",n,self.max_frame_len);返回Err(io::Error::new(io::ErrorKind::PermissionDenied,msg));}让mutframe=vec![0;n];self.inner.read_exact(&mutframe).await?;ok(frame)}最后,完全使用async/await写代码还有几个问题:asynctrait目前不支持Trait中的asyncfn,无法直接使用Trait抽象异步方法。临时解决方案是使用第三方库async-trait。如下:useasync_trait::async_trait;#[async_trait]traitAdvertisement{asyncfnrun(&self);}宏async_trait将代码转换为返回Pin>的同步方法。由于装箱和动态调度,性能上会有少量损失。目前异步销毁的drop方法必须是同步调用,不能使用await语法。当I/O对象在其生命周期结束后被析构时,一些I/O操作需要在底层句柄关闭之前完成。例如,通知网络对等点连接已关闭。在同步代码中,我们只需要将这些操作放在drop()中即可,但是在异步代码中,我们不能在drop()中做类似的事情。解决的办法是总是在异步I/O对象跨越生命周期之前显式执行关闭动作,或者实现一个类似GC的函数专门负责清理。展望在学习Rust的过程中,笔者主要关注网络相关的并发编程。因为之前有过ipfs/libp2pGo版本的开发经验,所以研究了rust-libp2p和nervostentacle。rust-libp2p是Parity实现的准官方版本,但是这个项目的代码极难理解,过于强调使用泛型参数的抽象,导致代码可读性很差。我咨询了代码的作者。他承认代码可能有点复杂,但他也强调是有原因的……nervostentacle的实现在协议方面并不完整,尤其是与标准的libp2p不兼容。两个项目的共同特点是代码主要以poll的形式编写,逻辑上嵌套了状态机。因此,作者尝试使用async/await彻底重构libp2p,参考rust-libp2p的实现,代码协程,向上层提供纯异步接口,力求在API层面接近go-libp2p的体验,这是为了推广Rust协议。是一种流程机制的尝试,也是个人学习的过程。目前刚刚开始,只完成了secio和yamux部分。在合适的时候,它会开源。希望更多的Rust爱好者共同开发完善。参考资料:AsynchronousDestructors深圳市星联网络科技有限公司(Netwarps),专注于互联网安全存储领域的技术研发与应用,是一家先进的安全存储基础设施提供商。其主要产品包括去中心化文件系统(DFS)、企业联盟链平台(EAC)、区块链操作系统(BOS)。微信公众号:网华

猜你喜欢