Rust异步之Future
对异步的学习,我们先从Future开始,学习异步的实现原理。等理解了异步是怎么实现的后,再学习Rust异步编程涉及的2个库(futures、tokio)的时候就容易理解多了。
Future
rust中Future
的定义如下,一个Future
可以理解为一段供将来调度执行的代码。我们为什么需要异步呢,异步相比同步高效在哪里呢?就是异步环境下,当前调用就绪时则执行,没有就绪时则不等待任务就绪,而是返回一个Future
,等待将来任务就绪时再调度执行。当然,这里返回Future
时关键的是要声明事件什么时候就绪,就绪后怎么唤醒这个任务到调度器去调度执行。
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[lang = "future_trait"]
pub trait Future { // A future represents an asynchronous computation.
type Output;
/* The core method of future, poll, attempts to resolve the future into a final value. This method does not block if the value is not ready. Instead, the current task is scheduled to be woken up when it's possible to make further progress by polling again. */
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
}
可以看到执行后的返回结果,一个是就绪返回执行结果,另一个是未就绪待定。
#[must_use = "this `Poll` may be a `Pending` variant, which should be handled"]
pub enum Poll<T> {
Ready(T),
Pending,
}
可能到这里你还是云里雾里,我们写一段代码,帮助你理解。完整代码见:future_study
use futures;
use std::{future::Future, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, thread, time::Duration};
fn main() {
// 我们现在还没有实现调度器,所以要用一下futues库里的一个调度器。
futures::executor::block_on(TimerFuture::new(Duration::new(10, 0)));
}
struct SharedState {
completed: bool,
waker: Option<Waker>,
}
// 我们想要实现一个定时器Future
pub struct TimerFuture {
share_state: Arc<Mutex<SharedState>>,
}
// impl Future trait for TimerFuture.
impl Future for TimerFuture {
type Output = ();
// executor will run this poll ,and Context is to tell future how to wakeup the task.
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut share_state = self.share_state.lock().unwrap();
if share_state.completed {
println!("future ready. execute poll to return.");
Poll::Ready(())
} else {
println!("future not ready, tell the future task how to wakeup to executor");
// 你要告诉future,当事件就绪后怎么唤醒任务去调度执行,而这个waker根具体的调度器有关
// 调度器执行的时候会将上下文信息传进来,里面最重要的一项就是Waker
share_state.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
impl TimerFuture {
pub fn new(duration: Duration) -> Self {
let share_state = Arc::new(Mutex::new(SharedState{completed:false, waker:None}));
let thread_shared_state = share_state.clone();
thread::spawn(move || {
thread::sleep(duration);
let mut share_state = thread_shared_state.lock().unwrap();
share_state.completed = true;
if let Some(waker) = share_state.waker.take() {
println!("detect future is ready, wakeup the future task to executor.");
waker.wake() // wakeup the future task to executor.
}
});
TimerFuture {share_state}
}
}
执行结果如下:
future not ready, tell the future task how to wakeup to executor
detect future is ready, wakeup the future task to executor.
future ready. execute poll to return.
可以看到,刚开始的时候,定时10s事件还未完成,处在Pending
状态,这时要告诉这个任务后面就绪后怎么唤醒去调度执行。等10s后,定时事件完成了,通过前面的设置的Waker
,唤醒这个Future
任务去调度执行。这里,我们看一下Context
和Waker
是怎么定义的:
/// The `Context` of an asynchronous task.
///
/// Currently, `Context` only serves to provide access to a `&Waker`
/// which can be used to wake the current task.
#[stable(feature = "futures_api", since = "1.36.0")]
pub struct Context<'a> {
waker: &'a Waker,
// Ensure we future-proof against variance changes by forcing
// the lifetime to be invariant (argument-position lifetimes
// are contravariant while return-position lifetimes are
// covariant).
_marker: PhantomData<fn(&'a ()) -> &'a ()>,
}
// A Waker is a handle for waking up a task by notifying its executor that it is ready to be run.
#[repr(transparent)]
#[stable(feature = "futures_api", since = "1.36.0")]
pub struct Waker {
waker: RawWaker,
}
现在你应该对Future
有新的理解了,上面的代码,我们并没有实现调度器,而是使用的futures
库中提供的一个调度器去执行,下面自己实现一个调度器,看一下它的原理。而在Rust中,真正要用的话,还是要学习tokio
库,这里我们只是为了讲述一下实现原理,以便于理解异步是怎么一回事。完整代码见:future_study, 关键代码如下:
use std::{future::Future, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, thread, time::Duration};
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
use futures::{future::{FutureExt, BoxFuture}, task::{ArcWake, waker_ref}};
use super::timefuture::*;
pub fn run_executor() {
let (executor, spawner) = new_executor_and_spawner();
// 将Future封装成一个任务,分发到调度器去执行
spawner.spawn( async {
let v = TimerFuture::new(Duration::new(10, 0)).await;
println!("return value: {}", v);
v
});
drop(spawner);
executor.run();
}
fn new_executor_and_spawner() -> (Executor, Spawner) {
const MAX_QUEUE_TASKS: usize = 10_000;
let (task_sender, ready_queue) = sync_channel(MAX_QUEUE_TASKS);
(Executor{ready_queue}, Spawner{task_sender})
}
// executor , received ready task to execute.
struct Executor {
ready_queue: Receiver<Arc<Task>>,
}
impl Executor {
// 实际运行具体的Future任务,不断的接收Future task执行。
fn run(&self) {
let mut count = 0;
while let Ok(task) = self.ready_queue.recv() {
count = count + 1;
println!("received task. {}", count);
let mut future_slot = task.future.lock().unwrap();
if let Some(mut future) = future_slot.take() {
let waker = waker_ref(&task);
let context = &mut Context::from_waker(&*waker);
if let Poll::Pending = future.as_mut().poll(context) {
*future_slot = Some(future);
println!("executor run the future task, but is not ready, create a future again.");
} else {
println!("executor run the future task, is ready. the future task is done.");
}
}
}
}
}
// 负责将一个Future封装成一个Task,分发到调度器去执行。
#[derive(Clone)]
struct Spawner {
task_sender: SyncSender<Arc<Task>>,
}
impl Spawner {
// encapsul a future object to task , wakeup to executor.
fn spawn(&self, future: impl Future<Output = String> + 'static + Send) {
let future = future.boxed();
let task = Arc::new(Task {
future: Mutex::new(Some(future)),
task_sender: self.task_sender.clone(),
});
println!("first dispatch the future task to executor.");
self.task_sender.send(task).expect("too many tasks queued.");
}
}
// 等待调度执行的Future任务,这个任务必须要实现ArcWake,表明怎么去唤醒任务去调度执行。
struct Task {
future: Mutex<Option<BoxFuture<'static, String>>>,
task_sender: SyncSender<Arc<Task>>,
}
impl ArcWake for Task {
// A way of waking up a specific task.
fn wake_by_ref(arc_self: &Arc<Self>) {
let clone = arc_self.clone();
arc_self.task_sender.send(clone).expect("too many tasks queued");
}
}
运行结果如下:
first dispatch the future task to executor.
received task. 1
future not ready, tell the future task how to wakeup to executor
executor run the future task, but is not ready, create a future again.
detect future is ready, wakeup the future task to executor.
received task. 2
future ready. execute poll to return.
return value: timer done.
executor run the future task, is ready. the future task is done.
第一次调度的时候,因为还没有就绪,在Pending状态,告诉这个任务,后面就绪是怎么唤醒该任务。然后当事件就绪的时候,因为前面告诉了如何唤醒,按方法唤醒了该任务去调度执行。其实,在实际应用场景中,难的地方还在于,你怎么知道什么时候事件就绪,去唤醒任务,我们很容易联想到Linux系统的epoll,tokio等底层,也是基于epoll实现的。通过epoll,我们就能方便的知道事件什么时候就绪了。
参考资料
主要学习资料如下:
上面的文章主要是学习异步的实现原理,理解异步是怎么实现的,而进行Rust异步编程时的具体实现,则主要依赖下面2个库:
学习这两个库的时候,一定要注意版本问题,这两个库最近变化的比较快,一定要学最新的。
Rust异步之Future的更多相关文章
- Netty 中的异步编程 Future 和 Promise
Netty 中大量 I/O 操作都是异步执行,本篇博文来聊聊 Netty 中的异步编程. Java Future 提供的异步模型 JDK 5 引入了 Future 模式.Future 接口是 Java ...
- Tokio,Rust异步编程实践之路
缘起 在许多编程语言里,我们都非常乐于去研究在这个语言中所使用的异步网络编程的框架,比如说Python的 Gevent.asyncio,Nginx 和 OpenResty,Go 等,今年年初我开始接触 ...
- Java异步调用Future对象
Future类存在于JDK的concurrent包中,主要用途是接收Java的异步线程计算返回的结果. 个人理解的使用场景大概如下: 有两个任务A和B,A任务中仅仅需要使用B任务计算成果,有两种方法实 ...
- java异步计算Future的使用(转)
从jdk1.5开始我们可以利用Future来跟踪异步计算的结果.在此之前主线程要想获得工作线程(异步计算线程)的结果是比较麻烦的事情,需要我们进行特殊的程序结构设计,比较繁琐而且容易出错.有了Futu ...
- flutter中的异步机制Future
饿补一下Flutter中Http请求的异步操作. Dart是一个单线程语言,可以理解成物理线路中的串联,当其遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到 ...
- 并发编程之Callable异步,Future模式
Callable 在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口.然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果.我们一般只能采用共享变 ...
- flutter中的异步机制 Future
饿补一下Flutter中Http请求的异步操作. Dart是一个单线程语言,可以理解成物理线路中的串联,当其遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到 ...
- Flutter 的异步机制Future
Dart是一个单线程语言,可以理解成物理线路中的串联,当其遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到卡顿,于是通常用异步处理来解决这个问题. Dart ...
- Dart异步编程-future
Dart异步编程包含两部分:Future和Stream 该篇文章中介绍Future 异步编程:Futures Dart是一个单线程编程语言.如果任何代码阻塞线程执行都会导致程序卡死.异步编程防止出现阻 ...
随机推荐
- HTML新特性--canvas绘图-文本
一.html5新特性--canvas绘图-文本(重点) #常用方法与属性 -ctx.strokeText(str,x,y); 绘制描边文字(空心) str:绘制文本 x,y:字符串左上角位置(以文 ...
- Docker镜像下载及加速器设置
docker远程仓库配置: vim ~/.docker/daemon.json { "registry-mirrors" : [ "https://registry.do ...
- mysql小白系列_09 mysql性能优化关键点
一 服务器参数调优,有哪些关键点? 1. 应用访问优化 优化方法 性能提升效果 优化成本 说明 减少数据访问能不访问就不访问-减少磁盘IO 1~1000 低 缓存服务器缓存mysql数据,Redis. ...
- INNODB 数据页结构
InnoDB DataPage 16384B 16K 38B FILE HEADER 56B PAGE HEADER RECORD Infimum + supremum Records UserRec ...
- Java并发包4--可重入锁ReentrantLock的实现原理
前言 ReentrantLock是JUC提供的可重入锁的实现,用法上几乎等同于Synchronized,但是ReentrantLock在功能的丰富性上要比Synchronized要强大. 一.Reen ...
- Spring 中基于 AOP 的 XML架构
Spring 中基于 AOP 的 XML架构 为了使用 aop 命名空间标签,你需要导入 spring-aop j架构,如下所述: <?xml version="1.0" e ...
- JPA EntityManager 在没有实体类的情况下返回Map
JPA entityManager.createNativeQuery()执行原生的SQL,当我们查询结果没有对应的实体类时,query.getResultList()返回的是一个List<Ob ...
- dsPIC33EP单片机的PPS(外设引脚选择)
利用dsPIC33EP单片机进行can通信的时候用到引脚复用 引脚复用通过查询数据手册: C1RX的寄存器为RPINR26.C1RXR=(设置为需要用到的引脚) 引脚设置为输入(C1RX),TRIS= ...
- [wordpress使用]001_环境安装
Wordpress强大的可扩展性和易用性等功能,使得越来越多的人选择它来建立自己的博客和网站.那么新手朋友该如何入手呢,今天制作这个教程就是旨在帮助新手朋友快速入门,从而为今后WP建站打下坚实的基础. ...
- 画出决策边界线--plot_2d_separator.py源代码【来自python机器学习基础教程】
import numpy as np import matplotlib.pyplot as plt from .plot_helpers import cm2, cm3, discrete_scat ...