V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
youngPacce
V2EX  ›  Rust

rust 关于.await 的疑惑

  •  
  •   youngPacce · 222 天前 · 1701 次点击
    这是一个创建于 222 天前的主题,其中的信息可能已经有所发展或是发生改变。

    佬们,最近在学习 rust 异步编程,rust 圣经里面有这一句话: <font color=blue>总之,在 async fn 函数中使用.await 可以等待另一个异步调用的完成。但是与 block_on 不同,.await 并不会阻塞当前的线程,而是异步的等待 Future A 的完成,在等待的过程中,该线程还可以继续执行其它的 Future B ,最终实现了并发处理的效果。</font>

    然后我就试了一下下面的这个用例,我预想的结果是 kitty 和 snoopy 先输出,等待 5 秒后再输出 world,但是结果是先输出 kitty,5s 后输出 snoopy,再 5s 输出 world,和文章中描述的好像不太对,请问这个 await 需要怎么理解?

    use futures::executor::block_on;
    use std::{thread, time};
    
    async fn hello_world() {
        hello_cat().await;
        hello_dog().await;
        println!("hello, world!");
    }
    
    async fn hello_cat() {
        println!("hello, kitty!");
        let ten_millis = time::Duration::from_secs(5);
        thread::sleep(ten_millis);
    }
    
    async fn hello_dog() {
        println!("hello, snoopy!");
        let ten_millis = time::Duration::from_secs(5);
        thread::sleep(ten_millis);
    }
    
    fn main() {
        let future = hello_world();
        block_on(future);
    }
    
    14 条回复    2024-06-14 16:37:06 +08:00
    aggron
        1
    aggron  
       222 天前
    thread::sleep 是阻塞的,要用 async 版本 sleep. async_std::task::sleep(xx).await / tokio::time::sleep(xx).await
    fcfangcc
        2
    fcfangcc  
       222 天前
    1 楼说得对,thread::sleep 的问题
    youngPacce
        3
    youngPacce  
    OP
       222 天前
    @aggron 谢谢我待会试试。
    fcfangcc
        4
    fcfangcc  
       222 天前
    补充下,即使改了 thread::sleep 也不行,hello_world 需要 join hello_cat 和 hello_dog 两个 future ,不然还是会先运行 hello_cat ,再运行 hello_dog
    raptium
        5
    raptium  
       222 天前
    只改 sleep 应该也不会是你想要的效果。
    .await 在逻辑上看起来还是阻塞的,只是它不会阻塞系统线程,线程还能去做别的事情。

    需要你的想要效果,cat 和 dog 应该是 spawn 出来跑。这个时候可以再看看 sleep 的效果,如果用了阻塞版 sleep ,那么即使 spawn 了,kitty 和 snoopy 之间也还是要等 5s ,因为 sleep 把线程阻塞了。
    i8086
        6
    i8086  
       222 天前
    编码方式的改变,语法糖最直观就是用同步写法写异步,而无需写回调。

    操作系统的改变,如:网络请求,同步写法等待响应时会阻塞线程,异步写法等待响应时不会占用线程。

    最大变化在于操作系统那一块,语法糖的便利性方便我们用好异步。
    jonah
        7
    jonah  
       222 天前
    你可能需要用 https://docs.rs/tokio/latest/tokio/task/fn.spawn.html ,将 future 转成异步执行。
    不然 hello_world 中的 hello_cat 和 hello_dog 两个 future 是顺序 poll 的,一个执行完再执行下一个。
    tootfsg
        8
    tootfsg  
       222 天前
    这是完全没有基础。
    0ioaths
        9
    0ioaths  
       222 天前
    将 `hello_cat` 和 `hello_dog` 作为异步任务执行就是期望的效果,以 `tokio runtime` 为例

    ```rust
    use tokio::{runtime, time};

    async fn hello_world() {
    tokio::spawn(hello_cat());
    tokio::spawn(hello_dog());

    let five_secs = time::Duration::from_secs(5);
    time::sleep(five_secs).await;
    println!("hello, world!");
    }

    async fn hello_cat() {
    println!("hello, kitty!");

    let three_secs = time::Duration::from_secs(3);
    time::sleep(three_secs).await;
    println!("hello, kitty!2");
    }

    async fn hello_dog() {
    println!("hello, snoopy!");
    }

    fn main(){
    runtime::Builder::new_multi_thread()
    .worker_threads(4)
    .enable_all()
    .build()
    .unwrap()
    .block_on(hello_world());
    }

    ```

    ```shell
    hello, kitty!
    hello, snoopy!
    hello, kitty!2 // after 3 sces
    hello, world! // after 5 secs
    ```
    my3157
        10
    my3157  
       222 天前
    async runtime 其实你找个最简单的实现, 看一下就知道了, 包括 tokio smol 这些大量的代码在 reactor, 利用 epoll, kqueue, iocp, io_uring 等各个平台上提供的机制吧默认的阻塞 io 实现成 async io, 不包含这部分的话代码量很小, tokio 稍微多一些, 实现也比较复杂, 比如调度和 work stealing, 理解机制的话最简单的可能也就几百行代码, 很好理解的
    nebkad
        11
    nebkad  
       222 天前   ❤️ 1
    你的疑惑不在于 await ,而是你还没理解 协程( coroutine) 和 线程 (thread) 的运行方式。
    协程是对线程的分时复用,线程是操作系统提供的对 CPU 的分时复用。
    Rust 的 await 是一种协程相关的关键字,你的理解是线程的工作方式。
    viruscamp
        12
    viruscamp  
       221 天前
    其实异步这里最好用 js 来理解 async 和 await ,js 还是一个典型的单线程 executor , 而你现在用的 futures::executor::block_on 也是单线程 executor .

    想达到你要的效果,要改两点:
    1. 同时发起 hello_cat().join(hello_dog()).await; // 相当于 js 的 Promise.all
    2. sleep 改异步的,比如这个 https://docs.rs/futures-time/latest/futures_time/task/fn.sleep.html

    如果是常用的多线程 executor 的话, thread::sleep 是可以达到你的效果的,但是错误用法。
    viruscamp
        13
    viruscamp  
       220 天前
    勘误:
    1. 同时发起 join!(hello_cat(), hello_dog()); // 相当于 js 的 Promise.all
    haharich
        14
    haharich  
       197 天前
    这应该算是异步编程的知识了
    1 、.await 不会阻塞当前线程主要是指当前线程如果此时有其他异步任务( Future )还是可以同时运行其他异步任务,并不会阻塞在只运行当前异步任务中(因为当前异步任务需要时间去完成),从现在的代码看,只有 hello_world()这个一个 Future ,所以体现不出来,假设还有一个 hello_world1()的 Future ,里面等待时间 2s ,然后不使用 block_on()去驱动这两个 Future ,而是使用 tokio 异步库的 join!()去并发驱动这两个 Future 就能看到其中一个在等待的同时可以运行另一个 Future 并输出日志(注意 thread::sleep(ten_millis) 需要换成异步然后 await 的方式,因为它是阻塞的,不然体现不出效果)
    3 、所以在当前的调用栈上,用 .await 就是要等待当前异步任务完成才能执行后面的逻辑,肯定是先输出 kitty ,hello_cat()的 Future 完成后,才会执行 hello_dog 输出 snoopy ,可以简单认为这里说的不阻塞跟当前调用栈没关系
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2420 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 15:58 · PVG 23:58 · LAX 07:58 · JFK 10:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.