首页 > 数据库 > 数据库开发技术 > 【翻译】从头实现Rust异步执行器
juliex版本。fn spawn<F, R>(future: F) -> JoinHandle<R>whereF: Future<Output = R> + Send + 'static,R: Send + 'static,{todo!()}
fn main() {futures::executor::block_on(async {let handle = spawn(async { 1 + 2 });assert_eq!(handle.await, 3);});}
type JoinHandle<R> = Pin<Box<dyn Future<Output = R> + Send;
use futures::channel::oneshot;fn spawn<F, R>(future: F) -> JoinHandle<R>whereF: Future<Output = R> + Send + 'static,R: Send + 'static,{let (s, r) = oneshot::channel();let future = async move {let _ = s.send(future.await);};todo!()Box::pin(async { r.await.unwrap() })}
struct Task {state: AtomicUsize,future: Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>,}
fn spawn<F, R>(future: F) -> JoinHandle<R>whereF: Future<Output = R> + Send + 'static,R: Send + 'static,{let (s, r) = oneshot::channel();let future = async move {let _ = s.send(future.await);};let task = Arc::new(Task {state: AtomicUsize::new(0),future: Mutex::new(Box::pin(future)),});QUEUE.send(task).unwrap();Box::pin(async { r.await.unwrap() })}
use crossbeam::channel;use once_cell::sync::Lazy;static QUEUE: Lazy<channel::Sender<Arc<Task= Lazy::new(|| {let (sender, receiver) = channel::unbounded::<Arc<Task>>();for _ in 0..num_cpus::get().max(1) {let receiver = receiver.clone();thread::spawn(move || receiver.iter().for_each(|task| task.run()));}sender});
nums_cpus提供。block_on()的前一篇博客文章中知道如何轮询future。impl Task {fn run(self: Arc<Task>) {let waker = todo!();let cx = &mut Context::from_waker(&waker);self.future.try_lock().unwrap().as_mut().poll(cx);}}
async_task::waker_fn(),但唤醒函数应该做什么呢?Arc<Task>放到QUEUE中,以下是我们应该考虑的潜在竞争冲突:impl Task {fn run(self: Arc<Task>) {let waker = async_task::waker_fn(|| {todo!("schedule if the task is not woken already and is not running");});let cx = &mut Context::from_waker(&waker);self.future.try_lock().unwrap().as_mut().poll(cx);todo!("schedule if the task was woken while running");}}
const WOKEN: usize = 0b01;const RUNNING: usize = 0b10;
let task = self.clone();let waker = async_task::waker_fn(move || {if task.state.fetch_or(WOKEN, Ordering::SeqCst) == 0 {QUEUE.send(task.clone()).unwrap();}});
self.state.store(RUNNING, Ordering::SeqCst);let cx = &mut Context::from_waker(&waker);let poll = self.future.try_lock().unwrap().as_mut().poll(cx);
RUNNING状态。这样future以后被唤醒后就不可能再次进入队列。Task结构体及其状态转换很有挑战,我感同身受。但也有好消息,这些工作都不需要你亲自做,使用asyc-task即可!async_task::Task()替换Arc<Task>,并用async-task::JoinHandle<()>替换oneshot通道。type Task = async_task::Task<()>;fn spawn<F, R>(future: F) -> JoinHandle<R>whereF: Future<Output = R> + Send + 'static,R: Send + 'static,{let (task, handle) = async_task::spawn(future, |t| QUEUE.send(t).unwrap(), ());task.schedule();Box::pin(async { handle.await.unwrap() })}
async_task::spawn()接受三个参数:run()在轮询future后执行.(),也就是忽略它.async_task::Task<()>,其中()就是刚刚传入的tag.async_task::JoinHandle<R, ()>,这里的()还是刚刚的tag. 这个JoinHandle是一个future,它完成的时候会返回一个Option<R>. 当返回None的时候表示任务发生了panic或者被取消了.schedule()方法,它只需调用任务上的 schedule 函数将其推入队列。我们也可以自己将任务推入QUEUE——最终结果是相同的。static QUEUE: Lazy<channel::Sender<Task= Lazy::new(|| {let (sender, receiver) = channel::unbounded::<Task>();for _ in 0..num_cpus::get().max(1) {let receiver = receiver.clone();thread::spawn(move || receiver.iter().for_each(|task| task.run()));}sender});type Task = async_task::Task<()>;type JoinHandle<R> = Pin<Box<dyn Future<Output = R> + Send;fn spawn<F, R>(future: F) -> JoinHandle<R>whereF: Future<Output = R> + Send + 'static,R: Send + 'static,{let (task, handle) = async_task::spawn(future, |t| QUEUE.send(t).unwrap(), ());task.schedule();Box::pin(async { handle.await.unwrap() })}
async_task::spawn()的好处不仅仅是简单。它也比我们自己写的Task更有效率,也更健壮。举一个健壮性的例子,async_task::Task在完成后立即删除未来,而不是等待任务的所有引用都失效后才删除。async-task 还提供了一些有用的特性,比如tags和cancellation,但是我们今天不讨论这些。还值得一提的是,async-task 是一个#[no_std]crate,甚至可以在没有标准库的情况下使用。Box::pin()分配。async_task::JoinHandle<R> 输出Option<R>,而JoinHandle 输出R:type JoinHandle<R> = async_task::JoinHandle<R, ()>;
async_task::JoinHandle封装到一个新的结构体中,如果任务发生panic或者被取消,它也会panic:这句话感觉说不通呢,需要看看async_task源码才行
struct JoinHandle<R>(async_task::JoinHandle<R, ()>);impl<R> Future for JoinHandle<R> {type Output = R;fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {match Pin::new(&mut self.0).poll(cx) {Poll::Pending => Poll::Pending,Poll::Ready(output) => Poll::Ready(output.expect("task failed")),}}}
run ()方法只是将恐慌传播到执行器中。我们应该思考这是否是我们真正想要的。use std::panic::catch_unwind;static QUEUE: Lazy<channel::Sender<Task= Lazy::new(|| {let (sender, receiver) = channel::unbounded::<Task>();for _ in 0..num_cpus::get().max(1) {let receiver = receiver.clone();thread::spawn(move || {receiver.iter().for_each(|task| {let _ = catch_unwind(|| task.run());})});}sender});
async-task库中提供的例子:JoinHandle<R>在await时也会发生恐慌JoinHandle<R>结果的那个任务中.JoinHandle<R>输出std::thread::Result<R>.async-task 有一个覆盖所有边缘情况的广泛测试套件,crossbeam的通道有许多测试,甚至通过Go和std::sync::mpsc测试套件,工作窃取双向队列基于一个经过形式证明的实现,而基于epoch的垃圾收集器也有正确性证明。每个“任务”需要一个分配,结果通常是每个连接需要一个分配。
原文作者:Rust语言中文社区;如有侵权,请联系管理员删除。