深度解析 mutex lock 方法为何返回 Result

279 阅读3分钟

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 时, 其中的过程那是相当地精彩,你可能会有以下的疑问:

  1. 程序会异常退出嘛?
  2. 如果程序不会异常退出时,那这么锁会释放嘛?
  3. 其他的 task 还能正常获取到锁嘛?
  4. ...

通过 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

所以,对于上述的几个疑问应该有了比较清晰的答案了

  1. 程序在没有 panic 拦截的情况下会退出
  2. 程序不退出的情况下,mutex 不会主动释放
  3. 其他的 task 只能获取到一把污染的锁

突然觉得 go 中的 defer 特性是真的好

谢谢!