【问题标题】:Concurrent async/await with sleep并发异步/等待与睡眠
【发布时间】:2022-02-04 05:19:52
【问题描述】:

我想知道this rather old question 关于期货的答案是否仍然适用于更新的语言结构async/await。似乎是这样,因为下面的代码打印:

hello 
good bye 
hello

虽然the guide

futures::join 宏可以等待多个不同的 futures 完成,同时同时执行它们。

很明显,对于sleep,这是许多其他异步系统(例如node.js)中预期行为的转移。

有什么根本原因会这样吗?

use std::time::Duration;
use std::thread;

async fn sayHiOne() {
    println!( " hello " );
    thread::sleep( Duration::from_millis( 3000 ) );
    println!( " good bye " );
} // ()

async fn sayHiTwo() {
    println!( " hello " );
} // ()

async fn mainAsync() {

    let fut1 = sayHiOne();

    let fut2 = sayHiTwo();

    futures::join!( fut1, fut2 );
} // ()

fn main() {
    block_on( mainAsync() );
} // ()

附加:实际线程预期的行为(I)

fn main() {

    let fut1 = do_async( move || {
        println!( "hello" );
        thread::sleep( Duration::from_millis( 3000 ) );
        println!( "good bye" );
    });

    let fut2 = do_async( move || {
        println!( "hello" );
    });

    fut1();
    fut2();

}

use std::thread;
use std::time::Duration;
use std::sync::mpsc::channel;


fn do_async<TOut, TFun>( foo: TFun ) -> (impl FnOnce()-> TOut)
 where
    TOut: Send + Sync + 'static,
    TFun: FnOnce() -> TOut + Send + Sync + 'static
    
{

    let (sender, receiver) 
        = channel::< TOut >();

    let hand = thread::spawn(move || {
        sender.send( foo() ).unwrap(); 
    } );

    let f = move || -> TOut {
        let res = receiver.recv().unwrap();
        hand.join().unwrap();
        return res;
    };

    return f;
} // ()

【问题讨论】:

  • 如果你使用 std 的睡眠,也就是阻塞睡眠,你显然不能期待好的结果。
  • 顺便说一句,我完全不明白你在问什么
  • @Stargateur 并发函数可以由单个线程执行,并且看起来仍然是并行的。如果其中一个执行阻塞操作,则线程跳转到另一个。如果不是这样,并且在 rust 中我们必须默认使用实际线程,那么具有并发异步函数的意义何在?
  • “如果其中一个执行阻塞操作,线程跳转到另一个”线程怎么知道你调用了阻塞函数,即使是这种情况,它还能做什么它 ?!?如果你想要异步行为,请使用来自 tokio 的 sleep async,你不能混合阻塞和异步,并且神奇地期望它会解决。 Rust 与操作系统很接近。操作系统有阻塞 api 和异步 api。如果你使用阻塞 api,你的选择不要怪异步 Rust。再说一次,我不明白你问什么。顺便说一句,异步 rust 非常非常难以理解。
  • @Stargateur 很明显,“并发系统”应该 - 在引擎盖下 - 不惜一切代价实现它所宣传的目标。毕竟,那是样板代码。

标签: rust async-await thread-sleep


【解决方案1】:

是的,它仍然适用。基本上必须是这样,因为就像链接的答案所说的那样,每个异步函数都将在同一个线程上运行 - std::thread::sleep 对异步一无所知,因此会使整个线程休眠。

Nodejs(和一般的 JavaScript)更多地围绕异步设计,因此语言原语和语言运行时以这种方式更加具有异步意识。

【讨论】:

  • 好吧,但是恕我直言std::thread::sleep 应该知道关于async一些事情,不是吗?至少自从 async/await 被引入 rust 以来。他们还应该了解任何其他阻塞功能,无论是否 I/O;正如所宣传的那样:futures::join 宏可以等待多个不同的未来完成,同时同时执行它们。
  • @cibercitizen1 我不同意,std::thread::sleepthread 模块中,所以它处理线程。但我来自一个更底层的角度:我认为异步函数只是美化的状态机。如果你来自不同的地方,比如 nodejs,那么这种行为很可能会令人惊讶。 futures::join 仍然在等待多个不同的期货,只是其中一个期货需要很长时间才能执行(想象一下,如果它正在执行计算或其他事情 - 它应该启动一个线程池吗?)
  • @Ikolbly 好吧,它很容易模拟实际并发行为(stackoverflow.com/a/70948063/286335)。如果 Rust 想要减轻这种样板代码并使我们免于错误,那并不难。 异步 Rust 的部分支持与同步 Rust 相同的稳定性保证。其他部分仍在成熟中,会随着时间而改变。 rust-lang.github.io/async-book/01_getting_started/…
  • @cibercitizen1 仅仅因为它可能休眠而为每个异步操作生成一个线程对我来说开销太大。我在微控制器上使用异步,异步的轻量级很有价值。
【解决方案2】:

由于标准/原始thread::sleep 是阻塞的,事实证明异步库提供了async_std::task::sleep( ... ),这是睡眠的非阻塞版本。它与.await(无括号)一起使用:

task::sleep::( Duration::from_millis( 1 ) ).await;

这个sleepunstable 版本的效果相同:yield_now 在某种意义上

将当前执行的future移到执行队列的后面,为其他future执行腾出空间。这在未来运行 CPU 密集型操作后特别有用。

所以我想,当一个任务计划执行一个长时间的工作时,预期的用途是“善意地”在未来之间共享线程的使用。

【讨论】:

    猜你喜欢
    • 2019-03-07
    • 2017-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-03
    • 2013-05-29
    • 2011-12-01
    • 2012-10-29
    相关资源
    最近更新 更多