You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,它支持同一线程递归获取锁,不会触发死锁。

步骤:

  1. 在Cargo.toml添加依赖:parking_lot = "0.12"
  2. 替换标准库的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 20:58:18