rust future async/await


futures库是很多人学习rust异步编程的第一站,今天我将通过一个简单的hello world程序,来揭开futures的执行细节。

本文使用rust 1.44.0 stable版本。

hello world

首先来一个异步执行的hello world:

use futures::executor;

async fn hello() {
    println!("Hello, world!");
}

fn main() {
    let fut = hello();
    executor::block_on(fut);
}

在hello函数打个断点,可以得出以下的调用栈:

hello::hello::{{closure}} main.rs:4
<core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll mod.rs:66
futures_executor::local_pool::block_on::{{closure}} local_pool.rs:317
futures_executor::local_pool::run_executor::{{closure}} local_pool.rs:87
std::thread::local::LocalKey<T>::try_with local.rs:263
std::thread::local::LocalKey<T>::with local.rs:239
futures_executor::local_pool::run_executor local_pool.rs:83
futures_executor::local_pool::block_on local_pool.rs:317
hello::main main.rs:9
std::rt::lang_start::{{closure}} rt.rs:67
std::rt::lang_start_internal::{{closure}} rt.rs:52
std::panicking::try::do_call panicking.rs:331
std::panicking::try panicking.rs:274
std::panic::catch_unwind panic.rs:394
std::rt::lang_start_internal rt.rs:51
std::rt::lang_start rt.rs:67
main 0x0000000000401b2c
__tmainCRTStartup 0x00000000004013c7
mainCRTStartup 0x00000000004014fb

executor

我们从main函数开始分析,main函数调用了futures::executor::block_on()函数

hello::main main.rs:9

block_on()会阻塞当前的线程,直到future执行完成。
run_executor()的参数是一个闭包,负责调用传进来的future。

pub fn block_on<F: Future>(f: F) -> F::Output {
    pin_mut!(f);
    run_executor(|cx| f.as_mut().poll(cx))
}

run_executor()会poll传进来的future,如果future是ready的,则退出loop并返回future::Output;否则,将线程挂起(park),等待唤醒(unpark)。

// Set up and run a basic single-threaded spawner loop, invoking `f` on each
// turn.
fn run_executor<T, F: FnMut(&mut Context<'_>) -> Poll<T>>(mut f: F) -> T {
    CURRENT_THREAD_NOTIFY.with(|thread_notify| {
        let waker = waker_ref(thread_notify);
        let mut cx = Context::from_waker(&waker);
        loop {
            if let Poll::Ready(t) = f(&mut cx) {
                return t;
            }
            // Consume the wakeup that occurred while executing `f`, if any.
            let unparked = thread_notify.unparked.swap(false, Ordering::Acquire);
            if !unparked {
                // No wakeup occurred. It may occur now, right before parking,
                // but in that case the token made available by `unpark()`
                // is guaranteed to still be available and `park()` is a no-op.
                thread::park();
                // When the thread is unparked, `unparked` will have been set
                // and needs to be unset before the next call to `f` to avoid
                // a redundant loop iteration.
                thread_notify.unparked.store(false, Ordering::Release);
            }
        }
    })
}

CURRENT_THREAD_NOTIFY是一个线程局部存储变量,由于ThreadNotify结构实现了ArcWake trait,所以可以通过futures::task::waker_ref()获得WakerRef,进而构造出context。

pub(crate) struct ThreadNotify {
    /// The (single) executor thread.
    thread: Thread,
    /// A flag to ensure a wakeup (i.e. `unpark()`) is not "forgotten"
    /// before the next `park()`, which may otherwise happen if the code
    /// being executed as part of the future(s) being polled makes use of
    /// park / unpark calls of its own, i.e. we cannot assume that no other
    /// code uses park / unpark on the executing `thread`.
    unparked: AtomicBool,
}

thread_local! {
    static CURRENT_THREAD_NOTIFY: Arc<ThreadNotify> = Arc::new(ThreadNotify {
        thread: thread::current(),
        unparked: AtomicBool::new(false),
    });
}

impl ArcWake for ThreadNotify {
    fn wake_by_ref(arc_self: &Arc<Self>) {
        // Make sure the wakeup is remembered until the next `park()`.
        let unparked = arc_self.unparked.swap(true, Ordering::Relaxed);
        if !unparked {
            // If the thread has not been unparked yet, it must be done
            // now. If it was actually parked, it will run again,
            // otherwise the token made available by `unpark`
            // may be consumed before reaching `park()`, but `unparked`
            // ensures it is not forgotten.
            arc_self.thread.unpark();
        }
    }
}

async

我们都听过,使用async修饰函数时,相当于返回一个实现了Future trait的trait object。比如上面的hello()等价于

fn hello() -> impl Future<Output = ()> {
	async {
		println!("Hello, world!");
	}
}

但是如果hello()里面有阻塞操作,比如睡眠了几秒钟,它是如何交出执行控制权?

async fn hello() {
	async_std::task::sleep(Duration::from_secs(3)).await;
    println!("Hello, world!");
}

答案是rust通过generator的yield实现跳转,进而实现交出执行控制权。

generator

Generator是rust异步执行的基础,在上文中,hello()的函数体会成为generator::resume的函数体,如果hello() 阻塞在操作,则由await调用generator的yield返回。

因此,使用async修饰函数时,其实是实现了一个包裹着generator的Future,这一点可以从代码得到验证。

/// Wrap a generator in a future.
///
/// This function returns a `GenFuture` underneath, but hides it in `impl Trait` to give
/// better error messages (`impl Future` rather than `GenFuture<[closure.....]>`).
pub const fn from_generator<T>(gen: T) -> impl Future<Output = T::Return>
where
    T: Generator<ResumeTy, Yield = ()>,
{
    struct GenFuture<T: Generator<ResumeTy, Yield = ()>>(T);

    // We rely on the fact that async/await futures are immovable in order to create
    // self-referential borrows in the underlying generator.
    impl<T: Generator<ResumeTy, Yield = ()>> !Unpin for GenFuture<T> {}

    impl<T: Generator<ResumeTy, Yield = ()>> Future for GenFuture<T> {
        type Output = T::Return;
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            // Safety: Safe because we're !Unpin + !Drop, and this is just a field projection.
            let gen = unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) };

            // Resume the generator, turning the `&mut Context` into a `NonNull` raw pointer. The
            // `.await` lowering will safely cast that back to a `&mut Context`.
            match gen.resume(ResumeTy(NonNull::from(cx).cast::<Context<'static>>())) {
                GeneratorState::Yielded(()) => Poll::Pending,
                GeneratorState::Complete(x) => Poll::Ready(x),
            }
        }
    }

    GenFuture(gen)
}

await

从上面的描述,不难想到,await其实是一个小型的executor,它会loop所等待的future,如果ready了,直接返回Future::Output,否则,通过generator::yield跳转,进而交出执行控制权。

loop {
	match polling_future_block() {
		Pending => yield,
		Ready(x) => break x
	}
}

future

future是可以嵌套的,也就是一个future可以包含另外一个future,比如上文中的task::sleep函数,调用形式是future.await,其实质是await去poll另外一个future。

如果最顶层的future阻塞了,线程将会被挂起(park),等待reactor唤醒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值