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)); }
问题分析
尝试代码的核心问题:
register_player方法既接收&self又传入game_arc,导致重复锁操作,逻辑冗余GameMap未做线程安全包装,无法在多线程环境下安全修改- 循环引用会导致内存泄漏(
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()); }
关键注意事项
- 避免死锁:保证锁的获取顺序一致(比如先锁Game再锁Player),不要在持有一个锁的情况下嵌套获取另一个相关锁
- 处理锁失败:实际项目中不要用
unwrap(),需通过match或if let处理PoisonError(锁被 panic 污染)和UpgradeError(Weak指针无法升级) - 内存泄漏避免:使用
Weak<Mutex<Game>>替代Arc<Mutex<Game>>,打破Player和Game之间的循环引用,确保资源能被正常回收
内容的提问来源于stack exchange,提问作者Vladimir Alinsky
相关产品推荐
相关产品推荐

