代码之家  ›  专栏  ›  技术社区  ›  ynn

在“等待”点上使用“std::sync::Mutex”是否总是导致死锁?

  •  2
  • ynn  · 技术社区  · 2 年前

    虽然我已经阅读了所有的OP,但的答案和评论 Why do I get a deadlock when 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任务 可以 自动移动到另一个工作线程( source ).

    因此,我认为如果任务(即。 f(mtx.clone(), 1).await , f(mtx.clone(), 2).await sleep(...).await 被选中的 (由Tokio运行时)在不同的线程中执行,但代码 作为运行时进行阻塞 发生 选择所有任务都在同一个线程中执行。

    我的理解正确吗?

    1 回复  |  直到 2 年前
        1
  •  2
  •   Chayim Friedman    2 年前

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

    的确

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

    是的,但这只适用于 tasks 。任务就像轻量级线程,它们可以在不同的操作系统线程上并行执行。但是 tokio::join!() 不会创建新任务。它是一个异步原语,在同一任务中,通过状态机将两个(或多个)未来合并为一个。这有一个优点,那就是它更轻,但这也意味着,如果你在其中一个期货中进行阻塞,所有其他期货也将被阻塞。因此,这对于真正绑定到IO的代码来说是很好的,如果代码绑定到CPU,哪怕是一点点,或者你有很多未来,那么最好生成一个任务。

    还要注意tokio任务 也许 在不同的线程上运行,但这不是 放心 。特别是tokio有优化 heuristics 这只能导致使用一个线程。在这种情况下,此代码也将死锁。此外,即使它不会死锁,它仍然是阻塞的,并且阻塞永远不应该在异步环境中进行。