跨“await”点使用“std::sync::Mutex”是否总是会导致死锁?

问题描述 投票:0回答:1

虽然我已经阅读了所有OP,答案和评论Why do I get a deadlock while using Tokio with a std::sync::Mutex?,我还不明白为什么代码在OP 永远阻塞。

这是原始代码稍作修改的版本:

use std::sync::Arc;
use std::sync::Mutex;
// use tokio::sync::Mutex;
use tokio::time::Duration;

async fn f(mtx: Arc<Mutex<i32>>, index: usize) {
    println!("{}: trying to lock...", index);
    {
        let mut v = mtx.lock().unwrap();
        // let mut v = mtx.lock().await;
        println!("{}: locked", index);
        tokio::time::sleep(Duration::from_millis(1)).await;
        *v += 1;
    }
    println!("{}: unlocked", index);
}

#[tokio::main]
async fn main() {
    let mtx = Arc::new(Mutex::new(0));
    tokio::join!(f(mtx.clone(), 1), f(mtx.clone(), 2));
}

输出是

1: trying to lock...
1: locked
2: trying to lock...

(and blocks forever...)

根据答案和评论(如果我没看错的话),原因是整个代码是在单线程环境中执行的。如果斜体部分为真,我可以理解阻塞行为。但是,我不明白斜体部分是否真的正确。

据我了解,

  • Tokio 的默认运行时是多线程的,除非您明确指定

    #[tokio::main(flavor = "current_thread")]
    source

  • await
    ed任务可以自动移动到另一个工作线程()。

所以我认为如果任务(即

f(mtx.clone(), 1).await
f(mtx.clone(), 2).await
sleep(...).await
选择(由Tokio运行时)在不同的线程中执行,代码不会阻塞,但代码看起来阻塞为运行时发生选择任务都在同一个单线程中执行。

我的理解正确吗?

asynchronous rust rust-tokio
1个回答
0
投票

整个代码在单线程环境下执行

确实如此。

Tokio 的默认运行时是多线程的

是的,但这仅适用于任务。任务就像轻量级线程,它们可以在不同的操作系统线程上并行执行。但是

tokio::join!()
不会创建新任务。它是一种异步原语,在同一任务上采用两个(或多个)Future 并通过状态机将它们组合成一个。这样做的优点是它更轻量级,但这也意味着如果您阻止其中一个期货,那么所有其他期货也将被阻止。因此,这对于真正 IO 密集型代码是有好处的,如果代码是 CPU 密集型(哪怕是一点点)或者你有很多 future,那么最好生成一个任务。

另请注意,tokio 任务可能在不同的线程上运行,但这不是保证。特别是,tokio 具有优化启发式,可以导致仅使用一个线程。在这种情况下,这段代码也会出现死锁。另外,即使不会死锁,它仍然是阻塞的,并且阻塞永远不应该在异步环境中进行。

© www.soinside.com 2019 - 2024. All rights reserved.