我在使用 Tokio 时偶然发现了一个死锁情况:
use tokio::time::{delay_for, Duration};
use std::sync::Mutex;
#[tokio::main]
async fn main() {
let mtx = Mutex::new(0);
tokio::join!(work(&mtx), work(&mtx));
println!("{}", *mtx.lock().unwrap());
}
async fn work(mtx: &Mutex<i32>) {
println!("lock");
{
let mut v = mtx.lock().unwrap();
println!("locked");
// slow redis network request
delay_for(Duration::from_millis(100)).await;
*v += 1;
}
println!("unlock")
}
产生以下输出,然后永远挂起。
lock
locked
lock
根据到 Tokio 文档 https://docs.rs/tokio/0.2.22/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use, using std::sync::Mutex
is ok:
与普遍的看法相反,在异步代码中使用标准库中的普通互斥体是可以的,而且通常是首选。
然而,更换Mutex
with a tokio::sync::Mutex
不会触发僵局,一切都“按预期”进行,但仅限于上面列出的示例情况。在现实场景中,如果延迟是由某些 Redis 请求引起的,它仍然会失败。
我认为这可能是因为我实际上根本没有生成线程,因此,即使“并行”执行,我也会锁定同一个线程,因为await只会产生执行。
在不产生单独线程的情况下实现我想要的目标的 Rustacean 方法是什么?
原因是这样的not OK使用一个std::sync::Mutex
这是你把它举过.await
观点。在这种情况下:
- 任务 1 持有互斥体,但被挂起
delay_for
.
- 任务 2 被调度并运行,但无法获取互斥体,因为它仍然属于任务 1。它将在获取互斥体时同步阻塞。
由于任务 2 被阻塞,这也意味着运行时线程被完全阻塞。它实际上无法进入其计时器处理状态(当运行时空闲并且不处理用户任务时发生这种情况),因此无法恢复任务 1。
因此,您现在观察到僵局。
==> 如果你需要跨过一个互斥锁.await
点你必须使用异步互斥体。正如 tokio 文档所述,同步互斥体可以与异步程序一起使用 - 但它们可能无法跨域保存.await
points.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)