rust 作为现代编程语言的 t0 级选手,并发与内存安全必然是标配. 作为保护临界资源读写的 mutex 也是必然支持的,我们在使用 mutex 的时候,lock() 方法返回的为何是 Result 的值呢?
作为测试, 我们很容易写出如下或者类似的代码:
use std::sync::Arc;
use std::sync::Mutex;
#[tokio::test]
async fn test() {
let lock = Arc::new(Mutex::new(1i32));
let l = lock.clone();
let h = tokio::task::spawn(async move {
let mut ac = l.lock().unwrap();
*ac += 1;
});
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let _ = h.await;
}
毫无疑问这段代码完全是可以通过测试的? 但是,如果我们的业务更加复杂涉及到多线程共享锁时,这个代码的复杂度就上去了。
多个线程或者异步的 task 共享一把锁时,当 task 发生 panic 时, 其中的过程那是相当地精彩,你可能会有以下的疑问:
- 程序会异常退出嘛?
- 如果程序不会异常退出时,那这么锁会释放嘛?
- 其他的
task还能正常获取到锁嘛? - ...
通过 mir 工具预处理代码
通过示例代码
use std::sync::Mutex;
use std::sync::Arc;
fn main() {
let lock = Arc::new(Mutex::new(0));
let l = lock.clone();
let mut data = l.lock().unwrap();
panic!("abc")
}
使用 mir 工具生成的代码如下:
fn main() -> () {
let mut _0: ();
let _1: std::sync::Arc<std::sync::Mutex<i32>>;
let mut _2: std::sync::Mutex<i32>;
let mut _4: &std::sync::Arc<std::sync::Mutex<i32>>;
let mut _6: std::result::Result<std::sync::MutexGuard<'_, i32>, std::sync::PoisonError<std::sync::MutexGuard<'_, i32>>>;
let _7: &std::sync::Mutex<i32>;
let mut _8: &std::sync::Arc<std::sync::Mutex<i32>>;
let _9: !;
let mut _10: std::fmt::Arguments<'_>;
scope 1 {
debug lock => _1;
let _3: std::sync::Arc<std::sync::Mutex<i32>>;
scope 2 {
debug l => _3;
let mut _5: std::sync::MutexGuard<'_, i32>;
scope 3 {
debug data => _5;
let mut _11: &[&str; 1];
}
}
}
bb0: {
_2 = Mutex::<i32>::new(const 0_i32) -> [return: bb1, unwind continue];
}
bb1: {
_1 = Arc::<Mutex<i32>>::new(move _2) -> [return: bb2, unwind continue];
}
bb2: {
_4 = &_1;
_3 = <Arc<Mutex<i32>> as Clone>::clone(move _4) -> [return: bb3, unwind: bb10];
}
bb3: {
_8 = &_3;
_7 = <Arc<Mutex<i32>> as Deref>::deref(move _8) -> [return: bb4, unwind: bb9];
}
bb4: {
_6 = Mutex::<i32>::lock(_7) -> [return: bb5, unwind: bb9];
}
bb5: {
_5 = Result::<MutexGuard<'_, i32>, PoisonError<MutexGuard<'_, i32>>>::unwrap(move _6) -> [return: bb6, unwind: bb9];
}
bb6: {
_11 = const main::promoted[0];
_10 = Arguments::<'_>::new_const::<1>(_11) -> [return: bb7, unwind: bb8];
}
bb7: {
_9 = panic_fmt(move _10) -> bb8;
}
bb8 (cleanup): {
drop(_5) -> [return: bb9, unwind terminate(cleanup)];
}
bb9 (cleanup): {
drop(_3) -> [return: bb10, unwind terminate(cleanup)];
}
bb10 (cleanup): {
drop(_1) -> [return: bb11, unwind terminate(cleanup)];
}
bb11 (cleanup): {
resume;
}
}
const main::promoted[0]: &[&str; 1] = {
let mut _0: &[&str; 1];
let mut _1: [&str; 1];
bb0: {
_1 = [const "abc"];
_0 = &_1;
return;
}
}
// 这一段是处理 panic 的
bb6: {
_11 = const main::promoted[0];
_10 = Arguments::<'_>::new_const::<1>(_11) -> [return: bb7, unwind: bb8];
}
bb7: {
_9 = panic_fmt(move _10) -> bb8;
}
// 这一段是 drop mutex
bb9 (cleanup): {
drop(_3) -> [return: bb10, unwind terminate(cleanup)];
}
很明显, 在 rust 中,各个变量在销毁的时候会自动调用 drop 方法, 如果在 mutex 调用 drop 方法之前,发生 panic 时, 当前 task 持有的锁是没有办法释放的。那么其他的 task 调用 lock 方法就会得到一个 Err。
所以,对于上述的几个疑问应该有了比较清晰的答案了
- 程序在没有 panic 拦截的情况下会退出
- 程序不退出的情况下,mutex 不会主动释放
- 其他的
task只能获取到一把污染的锁
突然觉得 go 中的 defer 特性是真的好
谢谢!