Rust中RwLock嵌套锁定问题:游戏引擎场景脚本交互求解
解决RwLock递归锁定导致的死锁问题(游戏引擎场景)
你遇到的核心问题是同一线程递归持有RwLock导致的死锁:游戏引擎每帧调用Scene的draw方法时,已经持有了Scene的锁;而脚本的update方法又尝试再次获取同一个Scene的锁,直接触发panic。
最小复现代码
use std::rc::Rc; use std::sync::RwLock; struct Script { pub scene: Rc<RwLock<Scene>>, } trait ScriptTrait { fn update(&self) -> (); } struct MyCustomizedScript { script : Script } impl ScriptTrait for MyCustomizedScript { fn update(&self) -> () { match self.script.scene.try_read() { Ok(n) => return (), Err(e) => panic!("err try write scene") } } } struct Object { scripts: Vec<Box<(dyn ScriptTrait)>> } impl Object { fn update_script(& mut self) -> () { for i in 0..self.scripts.len() { self.scripts[i].update(); } } } struct Scene { objects: Vec<Rc<RwLock<Object>>> } impl Scene { pub fn draw (&self) { for object in & self.objects{ let mut object = object.write().unwrap(); object.update_script(); //object.draw(); } } pub fn add_object(&mut self, object: Rc<RwLock<Object>>) -> (){ self.objects.push(object); } } fn main() { let scene = Rc::new(RwLock::new(Scene { objects: vec![] })); let customScript = Box::new(MyCustomizedScript { script: Script { scene : scene.clone() } }); let object = Rc::new(RwLock::new(Object { scripts: vec![customScript] })); let mut scene = scene.write().unwrap(); scene.add_object(object); scene.draw(); }
解决方案
1. 传递已持有的场景引用(最推荐,无额外依赖)
既然在Scene::draw方法里已经持有了Scene的有效引用,直接把这个引用传递给脚本的update方法,避免脚本重复加锁。
修改关键代码:
// 修改ScriptTrait,添加Scene引用参数 trait ScriptTrait { fn update(&self, scene: &Scene); } impl ScriptTrait for MyCustomizedScript { fn update(&self, scene: &Scene) { // 直接使用scene,无需再获取锁 // 例如调用scene.get_object_by_name(...) } } impl Object { // 接收Scene引用并传递给脚本 fn update_script(&mut self, scene: &Scene) { for script in &mut self.scripts { script.update(scene); } } } impl Scene { pub fn draw (&self) { for object in &self.objects { let mut object = object.write().unwrap(); // 直接传递当前Scene的引用 object.update_script(self); } } }
这种方式从根源消除了递归锁的可能,代码逻辑也更清晰。
2. 使用可重入锁(改动最小,但依赖第三方库)
如果不想大规模重构,可使用parking_lot库的ReentrantRwLock,它支持同一线程递归获取锁,不会触发死锁。
步骤:
- 在
Cargo.toml添加依赖:parking_lot = "0.12" - 替换标准库的
RwLock为parking_lot::ReentrantRwLock
修改后的核心片段:
use std::rc::Rc; use parking_lot::ReentrantRwLock; struct Script { pub scene: Rc<ReentrantRwLock<Scene>>, } // main初始化替换 let scene = Rc::new(ReentrantRwLock::new(Scene { objects: vec![] }));
注意:可重入锁虽能快速解决问题,但可能掩盖代码设计上的缺陷(比如过度依赖锁递归),仅建议作为临时方案。
3. 拆分锁粒度(长期优化方案)
重构代码结构,将Scene中需要被脚本频繁访问的部分(如对象查找)拆分出来,用独立的锁保护,避免整个Scene被大锁包裹。
示例:拆分出对象注册表
use std::collections::HashMap; struct ObjectRegistry { objects: HashMap<String, Rc<RwLock<Object>>>, } impl ObjectRegistry { fn get_object_by_name(&self, name: &str) -> Option<&Rc<RwLock<Object>>> { self.objects.get(name) } } struct Scene { objects: Vec<Rc<RwLock<Object>>>, object_registry: RwLock<ObjectRegistry>, } // 脚本仅持有注册表的引用,而非整个Scene struct Script { pub object_registry: Rc<RwLock<ObjectRegistry>>, }
这种方式让锁的粒度更细,既避免了递归锁问题,也提升了并发性能。
内容的提问来源于stack exchange,提问作者Charles Dewez
相关产品推荐
相关产品推荐

