asynchronous - 为什么将 Tokio 与 std::sync::Mutex 一起使用时会出现死锁?
问题描述
我在使用 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 docs,使用std::sync::Mutex
是可以的:
与流行的看法相反,在异步代码中使用标准库中的普通 Mutex 是可以的,并且通常更喜欢。
但是,用Mutex
a替换tokio::sync::Mutex
不会触发死锁,并且一切都“按预期”工作,但仅限于上面列出的示例情况。在现实世界的场景中,延迟是由一些 Redis 请求引起的,它仍然会失败。
我认为这可能是因为我实际上根本没有产生线程,因此,即使“并行”执行,我也会锁定同一个线程,因为 await 只会产生执行。
在不产生单独线程的情况下实现我想要的 Rustacean 方法是什么?
解决方案
在这里使用 a不好的原因std::sync::Mutex
是你把它放在了.await
点上。在这种情况下:
- 任务 1 持有互斥锁,但在
delay_for
. - 任务 2 被调度并运行,但无法获取 Mutex,因为它仍然由任务 1 拥有。它将在获取 Mutex 时同步阻塞。
由于任务 2 被阻塞,这也意味着运行时线程被完全阻塞。它实际上无法进入其计时器处理状态(当运行时空闲且不处理用户任务时发生),因此无法恢复任务 1。
因此,您现在正在观察死锁。
==> 如果您需要在一个.await
点上保持互斥锁,则必须使用异步互斥锁。如 tokio 文档所述,同步互斥锁可以与异步程序一起使用 - 但它们可能不会跨.await
点保存。
推荐阅读
- java - Spring Throws "ERROR SqlExceptionHelper: Already closed" after timeout
- android - 如何在 Android 中有效清除 Edittext
- html - href 到子域重定向到文件夹而不是子域
- reactjs - React Native - 在功能组件中创建方法并在组件外部调用此方法,可能吗?
- javascript - Javascript - 如果字符串包含任何不匹配正则表达式的字符,则 String.search() 返回 true
- flutter - Flutter:OrientationBuilder - 重用子小部件而不重建?
- java - 如何仅使用 EasyMock 模拟静态 void 方法?
- drupal - Drush 安装 - 整合/现场流程
- c# - 使用匿名类型时,模拟方法返回 null
- doctrine - Shopware 5 PhpStan 分析中的“不接受 Doctrine\ORM\EntityRepository”错误