为何在函数参数中处理Mutex解锁会导致程序永久阻塞?
这问题挺典型的,咱们一步步拆解来搞明白为啥会这样:
首先看第一个能正常跑完的代码:
use std::sync::Mutex; fn f(_: usize, _: usize) {} fn main() { let m = Mutex::new(0); let i = { *m.lock().unwrap() }; let j = { *m.lock().unwrap() }; f(i, j); }
这里的花括号是独立的小代码块,m.lock().unwrap()返回的MutexGuard(锁的守卫)在代码块结束后就会自动被销毁——因为咱们只是把它解引用后的值存到了i里,代码块执行完,守卫就离开了自己的作用域,锁也就跟着释放了。等咱们去获取j的时候,锁已经是空闲状态了,自然不会有问题。
再看第二个同样能正常运行的代码:
use std::sync::Mutex; fn f(_: usize, _: usize) {} fn main() { let m = Mutex::new(0); f( { let i = *m.lock().unwrap(); i }, { let j = *m.lock().unwrap(); j }, ); }
每个参数里的花括号也是独立的作用域。第一个代码块执行时,获取锁、解引用拿到值存到i,代码块一结束,守卫就被销毁,锁释放。之后才会执行第二个代码块的逻辑,这时候再拿锁完全没问题,函数调用自然能顺利完成。
重点来了,看这个会永久阻塞的代码:
use std::sync::Mutex; fn f(_: usize, _: usize) {} fn main() { let m = Mutex::new(0); f({ *m.lock().unwrap() }, { *m.lock().unwrap() }); }
这里的核心坑点是Rust中函数参数临时变量的存活时间规则:函数调用时,所有参数的临时值(包括这里的MutexGuard),都会存活到整个函数调用完成之后才会被销毁。
也就是说,第一个参数里的{ *m.lock().unwrap() },虽然你解引用取了数值,但生成的MutexGuard并不会在花括号结束后就释放——它作为函数参数的临时依赖,要等到f()整个调用彻底结束才会被drop掉。
那流程就变成了:先求值第一个参数,获取锁,守卫一直活着;然后求值第二个参数,又去尝试获取同一个Mutex的锁,这时候第一个锁还死死攥着没释放,直接就造成了死锁,程序自然就永久阻塞住了。
简单总结:前两个例子里,我们用独立代码块限制了MutexGuard的作用域,代码块结束锁就释放;第三个例子里,守卫的存活时间被延长到了整个函数调用期间,导致第二个参数拿锁时第一个锁还没松,最终死锁。
备注:内容来源于stack exchange,提问作者ynn

