Rust中使用lazy_static+RwLock读写自定义结构体时读操作触发死锁
解决RwLock持有写锁时读操作触发死锁的问题
这个死锁问题其实很好定位——你在同一个线程里,握着写锁的同时又去抢读锁了!Rust标准库的RwLock是不支持重入的,也就是说同一个线程不能在持有锁(不管是读还是写状态)的情况下,再次获取这把锁,否则就会触发死锁检测的panic,也就是你看到的那条错误信息。
咱们看你的代码:在auth函数里,你先通过LATEST_AUTH.write().unwrap()拿到了写锁,这个锁会一直被持有,直到au变量离开作用域。结果你紧接着就调用LATEST_AUTH.read().unwrap(),这相当于同一个线程在握着写锁的同时,又伸手去拿读锁,直接触发了死锁。
给你两个简单的解决办法:
办法一:直接用已持有的写锁访问数据
既然你已经拿到了写锁,那不管是读还是写数据都没问题,完全不需要再去申请读锁。直接用au这个引用读取就行:
pub fn auth(){ let api_resp: ApiResponse = res.json().unwrap(); //From a reqwest res let mut au = LATEST_AUTH.write().unwrap(); au.access_token = api_resp.access_token.clone(); // 直接用au访问数据,不用再获取读锁 println!("LATEST_AUTH access token: {}", au.access_token); // 如果想打印整个实例,可以给Authentication实现Debug后用{:?} // println!("LATEST_AUTH: {:?}", au); }
办法二:先释放写锁,再获取读锁
如果确实需要在写操作完成后,重新通过读锁访问数据,那你可以手动缩小写锁的作用域,或者主动释放写锁:
pub fn auth(){ let api_resp: ApiResponse = res.json().unwrap(); //From a reqwest res // 用花括号缩小写锁的作用域 { let mut au = LATEST_AUTH.write().unwrap(); au.access_token = api_resp.access_token.clone(); } // 这里au离开作用域,写锁自动释放 // 现在可以安全获取读锁了 let auth_read = LATEST_AUTH.read().unwrap(); println!("LATEST_AUTH: {}", auth_read); }
或者手动调用drop释放写锁:
pub fn auth(){ let api_resp: ApiResponse = res.json().unwrap(); //From a reqwest res let mut au = LATEST_AUTH.write().unwrap(); au.access_token = api_resp.access_token.clone(); drop(au); // 主动释放写锁 let auth_read = LATEST_AUTH.read().unwrap(); println!("LATEST_AUTH: {}", auth_read); }
另外提一句,你用lazy_static初始化RwLock的方式是完全没问题的,核心问题就是锁的重入尝试。记住Rust标准库的RwLock没有重入能力,不管是读锁重复获取,还是写锁转读锁,同一个线程里这么做都会触发死锁panic哦。
内容的提问来源于stack exchange,提问作者Adrien Chapelet




