虽然我已经阅读了所有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运行时)在不同的线程中执行,代码不会阻塞,但代码看起来阻塞为运行时发生选择任务都在同一个单线程中执行。
我的理解正确吗?
整个代码在单线程环境下执行
确实如此。
Tokio 的默认运行时是多线程的
是的,但这仅适用于任务。任务就像轻量级线程,它们可以在不同的操作系统线程上并行执行。但是
tokio::join!()
不会创建新任务。它是一种异步原语,在同一任务上采用两个(或多个)Future 并通过状态机将它们组合成一个。这样做的优点是它更轻量级,但这也意味着如果您阻止其中一个期货,那么所有其他期货也将被阻止。因此,这对于真正 IO 密集型代码是有好处的,如果代码是 CPU 密集型(哪怕是一点点)或者你有很多 future,那么最好生成一个任务。
另请注意,tokio 任务可能在不同的线程上运行,但这不是保证。特别是,tokio 具有优化启发式,可以导致仅使用一个线程。在这种情况下,这段代码也会出现死锁。另外,即使不会死锁,它仍然是阻塞的,并且阻塞永远不应该在异步环境中进行。