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

Rust中如何实现线程安全的可变交叉引用?

Rust线程安全的双向引用结构体实现

问题描述

需要实现一对可互相引用、修改且线程安全的结构体Player和Game,初始代码及期望使用接口如下:

初始结构体定义

pub struct Player {
    pub x: f32,
    pub y: f32,
    game: Option<Game>, // 引用Game
}

impl Player {
    pub fn new(x: f32, y: f32) -> Self {    
        Player { x, y, game: None}
    }

    // 可修改self、game及map的函数
}

pub struct Game {
    pub map: GameMap,
    players: Vec<Player>, // 引用Player
}

impl Game {
    pub fn new() -> Self {
        Game { map: GameMap::new(), players: Vec::new()}
    }

    pub fn register_player(&mut self, player: Player) {
        todo!();
    }
}

期望使用接口

fn main() {
    let mut p1 = Player::new(0.0, 0.0);
    let mut p2 = Player::new(100.0, 100.0);

    let mut game = Game::new();
    game.register_player(p1);
    game.register_player(p2);

    p1.forward();  // 借助game的map修改自身坐标
    p2.shoot();  // 修改map,可能修改其他玩家
}

由于要保证线程安全,无法使用Rc/RefCell,尝试Arc<Mutex>实现但未成功,附上尝试代码:

use std::sync::{Arc, Mutex};

pub struct Player {
    pub x: f32,
    pub y: f32,
    game: Option<Arc<Mutex<Game>>>,
}

impl Player {
    pub fn create(x: f32, y: f32) -> Arc<Mutex<Self>> {
        let mut player = Player {
            x,
            y,
            game: None,
        };
        Arc::new(Mutex::new(player))
    }

    pub fn mount_game(&mut self, game: Arc<Mutex<Game>>) {
        self.game = Some(game);
    }
}

pub struct Game {
    players: Vec<Arc<Mutex<Player>>>,
}

impl Game {
    pub fn create() -> Arc<Mutex<Self>> {
        let mut game = Game {
            players: Vec::new(),
        };
        Arc::new(Mutex::new(game))
    }

    pub fn register_player(&self, game_arc: Arc<Mutex<Self>>, player_arc: Arc<Mutex<Player>>) {
        let mut game = game_arc.lock().unwrap();
        game.players.push(Arc::clone(&player_arc));
        player_arc.lock().unwrap().mount_game(Arc::clone(&game_arc));
    }
}

fn main() {
    let mut p1 = Player::create(0.0, 0.0);
    let mut p2 = Player::create(0.0, 0.0);
    let mut game = Game::create();
    game.lock().unwrap().register_player(Arc::clone(&game), Arc::clone(&p1));
    game.lock().unwrap().register_player(Arc::clone(&game), Arc::clone(&p2));
}

问题分析

尝试代码的核心问题:

  1. register_player方法既接收&self又传入game_arc,导致重复锁操作,逻辑冗余
  2. GameMap未做线程安全包装,无法在多线程环境下安全修改
  3. 循环引用会导致内存泄漏(Arc的引用计数无法归零)

解决方案

以下是修正后的完整实现,包含线程安全保障、双向引用及核心功能:

use std::sync::{Arc, Mutex, Weak};

// 定义GameMap,添加Mutex保证线程安全
#[derive(Default)]
pub struct GameMap {
    obstacles: Vec<(f32, f32)> // 示例字段,按需扩展
}

pub struct Player {
    pub x: f32,
    pub y: f32,
    game: Option<Weak<Mutex<Game>>>, // 使用Weak避免循环引用导致内存泄漏
}

impl Player {
    // 创建Player并直接包装为Arc<Mutex>
    pub fn new(x: f32, y: f32) -> Arc<Mutex<Self>> {
        Arc::new(Mutex::new(Player {
            x,
            y,
            game: None,
        }))
    }

    // 设置关联的Game(内部方法)
    fn set_game(&mut self, game: Weak<Mutex<Game>>) {
        self.game = Some(game);
    }

    // 示例:根据GameMap移动玩家
    pub fn forward(&mut self, distance: f32) {
        if let Some(game_weak) = &self.game {
            // 尝试将Weak升级为Arc,若Game已被销毁则跳过
            if let Some(game_lock) = game_weak.upgrade() {
                let game = game_lock.lock().unwrap();
                // 此处可读取GameMap数据做碰撞检测等逻辑,示例直接移动
                self.x += distance;
            }
        }
    }

    // 示例:射击操作,修改地图和目标玩家
    pub fn shoot(&mut self, target: Arc<Mutex<Player>>) {
        if let Some(game_weak) = &self.game {
            if let Some(game_lock) = game_weak.upgrade() {
                let mut game = game_lock.lock().unwrap();
                // 修改地图:添加弹痕
                game.map.lock().unwrap().obstacles.push((self.x, self.y));
                
                // 修改目标玩家位置
                let mut target_player = target.lock().unwrap();
                target_player.x += 5.0;
            }
        }
    }
}

pub struct Game {
    pub map: Mutex<GameMap>, // GameMap用Mutex包裹保证线程安全
    players: Vec<Arc<Mutex<Player>>>,
}

impl Game {
    // 创建Game并包装为Arc<Mutex>
    pub fn new() -> Arc<Mutex<Self>> {
        Arc::new(Mutex::new(Game {
            map: Mutex::new(GameMap::default()),
            players: Vec::new(),
        }))
    }

    // 关联函数:注册玩家,建立双向引用
    pub fn add_player(game_arc: Arc<Mutex<Self>>, player: Arc<Mutex<Player>>) {
        let mut game = game_arc.lock().unwrap();
        // 给Player设置Game的Weak引用
        let mut player_mut = player.lock().unwrap();
        player_mut.set_game(Arc::downgrade(&game_arc));
        // 将Player加入Game的玩家列表
        game.players.push(player);
    }
}

fn main() {
    let p1 = Player::new(0.0, 0.0);
    let p2 = Player::new(100.0, 100.0);
    let game = Game::new();

    // 注册玩家
    Game::add_player(Arc::clone(&game), Arc::clone(&p1));
    Game::add_player(Arc::clone(&game), Arc::clone(&p2));

    // 测试移动功能
    p1.lock().unwrap().forward(10.0);
    let p1_state = p1.lock().unwrap();
    println!("p1移动后位置:({}, {})", p1_state.x, p1_state.y);

    // 测试射击功能
    p2.lock().unwrap().shoot(Arc::clone(&p1));
    let p1_state = p1.lock().unwrap();
    println!("p1被射击后位置:({}, {})", p1_state.x, p1_state.y);
    
    let game_map = game.lock().unwrap().map.lock().unwrap();
    println!("地图障碍物数量:{}", game_map.obstacles.len());
}

关键注意事项

  1. 避免死锁:保证锁的获取顺序一致(比如先锁Game再锁Player),不要在持有一个锁的情况下嵌套获取另一个相关锁
  2. 处理锁失败:实际项目中不要用unwrap(),需通过match或if let处理PoisonError(锁被 panic 污染)和UpgradeError(Weak指针无法升级)
  3. 内存泄漏避免:使用Weak<Mutex<Game>>替代Arc<Mutex<Game>>,打破Player和Game之间的循环引用,确保资源能被正常回收

内容的提问来源于stack exchange,提问作者Vladimir Alinsky

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 21:14:55