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

如何在Tokio应用中借用HashMap存储的值给函数

解决方案:用Arc<tokio::sync::Mutex<Game>>实现共享可变访问

你的核心问题是需要让GameManager的HashMap和game_loop任务共享同一个Game实例。克隆会产生独立副本,自然无法同步状态,而Arc<Mutex>正是Rust中跨线程/任务共享可变数据的标准方案,结合Tokio异步环境,我们需要用Tokio提供的异步Mutex(避免阻塞线程)。

步骤1:调整GameManager的结构体定义

把games的类型从HashMap<Uuid, Game>改为HashMap<Uuid, Arc<tokio::sync::Mutex<Game>>>,这样每个Game都被包装在原子引用计数的异步互斥锁中:

use tokio::sync::Mutex;
use std::sync::Arc;

pub struct GameManager {
    games: HashMap<Uuid, Arc<Mutex<Game>>>,
    game_channels: HashMap<Uuid, UnboundedSender<GameEvent>>,
    broadcast: UnboundedSender<ServerEvent>,
}

步骤2:修改find_or_create_game方法

创建Game后,用Arc::new(Mutex::new(game))包装,插入HashMap的同时,克隆一份Arc传给game_loop(Arc的克隆是轻量的,只是增加引用计数):

impl GameManager {
    async fn find_or_create_game(&mut self, user_options: &GameOptions) -> Uuid {
        // 遍历查找可加入的游戏,需要异步获取锁访问Game方法
        for game_lock in self.games.values() {
            let game = game_lock.lock().await;
            println!("game id {:?}", game.id);
            println!("game players {:?}", game.state.get_players());
            if game.allows_joining() && game.matches_player_options(user_options) {
                println!("Joining existing game");
                return game.id;
            }
        }

        // 创建新游戏
        let rng = ::rand::rngs::StdRng::from_seed(OsRng.gen());
        let game = Game::new(Some(user_options.clone()), rng);
        let game_id = game.id;
        let (game_sender, game_receiver) = mpsc::unbounded_channel::<GameEvent>();
        let broadcast = self.broadcast.clone();

        // 包装Game并插入HashMap
        let game_arc = Arc::new(Mutex::new(game));
        self.game_channels.insert(game_id, game_sender.clone());
        self.games.insert(game_id, game_arc.clone());

        // 传给game_loop的是Arc的克隆
        tokio::spawn(game_loop(game_arc, broadcast, game_receiver));
        game_id
    }
}

步骤3:修改game_loop函数

将参数从mut game: Game改为game_arc: Arc<Mutex<Game>>,每次需要访问或修改Game时,通过lock().await获取可变引用:

pub async fn game_loop(
    game_arc: Arc<Mutex<Game>>,
    broadcast: UnboundedSender<ServerEvent>,
    mut receiver: UnboundedReceiver<GameEvent>,
) -> Result<(), io::Error> {
    // 先获取一次Game计算帧间隔
    let dur = {
        let game = game_arc.lock().await;
        std::time::Duration::from_secs_f64(1.0 / game.state.options.fps as f64)
    };
    let mut interval = tokio::time::interval(dur);

    loop {
        interval.tick().await;

        // 处理事件:每次处理都需要锁Game
        while let Some(event) = receiver.recv().await {
            let mut game = game_arc.lock().await;
            handle_game_event(event, &mut game, &broadcast);
        }

        // 检查游戏状态并执行tick
        let mut game = game_arc.lock().await;
        if game.has_ended() {
            break;
        } else {
            game.tick();
            let _ = broadcast.send(ServerEvent::Tick(game.get_tick()));
        }
    }
    Ok(())
}

关键注意事项

  • 使用Tokio的Mutex:tokio::sync::Mutex是为异步场景设计的,lock()返回Future,通过await等待不会阻塞线程;而std::sync::Mutex在异步环境中会导致线程阻塞,必须避免。
  • 锁的粒度:尽量缩小持有锁的范围,比如计算帧间隔时用代码块限制锁的持有时间,减少锁竞争。
  • 引用计数:Arc的克隆是原子操作,线程安全,适合跨任务共享;当最后一个Arc被销毁时,Game才会被自动释放。

这样修改后,GameManager的HashMap和game_loop任务就共享同一个Game实例了,两边的修改都会同步到同一数据上。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 14:30:53