内容简介:As of stableA very basic simple coroutine library contains only an event-less 'yield' primitive, which stops execution of the current coroutine so that other coroutines can run. This is the kind of library that I chose to demonstrate in this post to provid
As of stable Rust
1.39.0, it is possible to
implement a very basic and safe coroutine
library using Rust's
async
/ await
support, and
in under 100 lines of code. The implementation depends solely on std
and is
stack-less (meaning, not depending on an separate CPU architecture stack).
A very basic simple coroutine library contains only an event-less 'yield' primitive, which stops execution of the current coroutine so that other coroutines can run. This is the kind of library that I chose to demonstrate in this post to provide the most concise example.
Yielder
To the coroutine we pass a Fib
struct that only contains a simple binary
state. This Fib
struct has a waiter
method that creates a
Future
that the
coroutine can use in order to be await
ed upon.
use std::future::Future; use std::pin::Pin; use std::task::{Poll, Context}; enum State { Halted, Running, } struct Fib { state: State, } impl Fib { fn waiter<'a>(&'a mut self) -> Waiter<'a> { Waiter { fib: self } } } struct Waiter<'a> { fib: &'a mut Fib, } impl<'a> Future for Waiter<'a> { type Output = (); fn poll(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> { match self.fib.state { State::Halted => { self.fib.state = State::Running; Poll::Ready(()) } State::Running => { self.fib.state = State::Halted; Poll::Pending } } } }
Executor
Our executor keeps a vector of uncompleted futures, where the state of each
future is located on the heap. As a very basic executor, it only supports
adding futures to it before actual execution takes place and not afterward.
The push
method adds a closure to the list of futures, and the run
method
performs interlaced execution of futures until all of them complete.
use std::collections::VecDeque; struct Executor { fibs: VecDeque<Pin<Box<dyn Future<Output=()>>>>, } impl Executor { fn new() -> Self { Executor { fibs: VecDeque::new(), } } fn push<C, F>(&mut self, closure: C) where F: Future<Output=()> + 'static, C: FnOnce(Fib) -> F, { let fib = Fib { state: State::Running }; self.fibs.push_back(Box::pin(closure(fib))); } fn run(&mut self) { let waker = waker::create(); let mut context = Context::from_waker(&waker); while let Some(mut fib) = self.fibs.pop_front() { match fib.as_mut().poll(&mut context) { Poll::Pending => { self.fibs.push_back(fib); }, Poll::Ready(()) => {}, } } } }
Null Waker
For the executor implementation above, we need a null Waker , similar to the one used in genawaiter ( link ).
use std::task::{RawWaker, RawWakerVTable, Waker}, pub fn create() -> Waker { // Safety: The waker points to a vtable with functions that do nothing. Doing // nothing is memory-safe. unsafe { Waker::from_raw(RAW_WAKER) } } const RAW_WAKER: RawWaker = RawWaker::new(std::ptr::null(), &VTABLE); const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); unsafe fn clone(_: *const ()) -> RawWaker { RAW_WAKER } unsafe fn wake(_: *const ()) { } unsafe fn wake_by_ref(_: *const ()) { } unsafe fn drop(_: *const ()) { }
Giving it a go
We can test the library using a program such as the following:
pub fn main() { let mut exec = Executor::new(); for instance in 1..=3 { exec.push(move |mut fib| async move { println!("{} A", instance); fib.waiter().await; println!("{} B", instance); fib.waiter().await; println!("{} C", instance); fib.waiter().await; println!("{} D", instance); }); } println!("Running"); exec.run(); println!("Done"); }
The output is as follows:
Running 1 A 2 A 3 A 1 B 2 B 3 B 1 C 2 C 3 C 1 D 2 D 3 D Done
Performance
Timing the following program compiled with lto = true
, I have seen that it
takes about 5 nanoseconds for each iteration of the internal loop, on an Intel
i7-7820X CPU.
pub fn bench() { let mut exec = Executor::new(); for _ in 1..=2 { exec.push(move |mut fib| async move { for _ in 0..100_000_000 { fib.waiter().await; } }); } println!("Running"); exec.run(); println!("Done"); }
End notes
One of the nice things about the async
/ await
support in the Rust compiler
is that it does not depend on any specific run-time. Thus, if you commit to
certain run-time, you are free to implement your own executor.
Independency of run-time has its downsides. For example, the library presented
here is not compatible with other run-times such as
async-std
. And in fact, the implementation
violates the interface intended for the Future
's poll
function by assuming
that the Future
will always be Ready
after it was Pending
.
Combined uses of several run-times in a single program is possible but requires extra care (see Reddit discussion ).
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
单元测试之道Java版
David Thomas、Andrew Hunt / 陈伟柱、陶文 / 电子工业 / 2005-1 / 25.00元
程序员修炼三部曲丛书包含了四本书,介绍了每个注重实效的程序员和成功团队所必备的一些工具。 注重实效的程序员都会利用反馈来指导开发,并驱动个人的开发流程。编码的时候,最有用的反馈来自于“单元测试”。 为了测试一座桥梁,不会只在晴朗的天气,开一辆汽车从桥中间穿过,就认为已经完成了对桥梁的测试。然而许多程序员却正在使用这种测试方法——把这种一次顺利通过称为“测试”。事实上,注重实效的程序员应......一起来看看 《单元测试之道Java版》 这本书的介绍吧!