Rust多线程问题:循环中Engine所有权移动导致的编译错误
我是多线程(thread)、异步、并行与并发领域的新手,不确定标题中'thread'的用法是否准确。近期我在开发一款四子棋(Connect 4)游戏引擎,该引擎可在控制台显示游戏状态、验证落子有效性、判断胜负等,同时实现了人类玩家、暴力搜索AI和蒙特卡洛AI。由于蒙特卡洛AI需要大量计算,且计算时间越长落子选择越优,我希望利用人类玩家思考落子的时间让蒙特卡洛AI并行计算,因此尝试用Rust的thread和mpsc通道实现多线程逻辑。
简化版实现代码
use std::thread; use std::sync::mpsc; struct Engine { } // Engine that plays the game. There are different ones in the actual game. This is for simplicity's sake impl Engine { pub fn new() -> Engine { Engine {} } pub fn make_move(&mut self, gamestate: u32) { println!("Player made move: {}", gamestate + 2); } } fn main() { let mut player_blue = Engine::new(); let mut player_red = Engine::new(); for _ in 0..2 { let (tx_blue, rx_blue) = mpsc::channel(); let (tx_blue_back, rx_blue_back) = mpsc::channel(); let manager_blue = thread::spawn(move || { let mut counter = 0; loop { if let Ok(t) = rx_blue.try_recv() { player_blue.make_move(t); tx_blue_back.send(counter).unwrap(); break; } counter += 1; } }); let manager_red = thread::spawn(move || { let mut input = String::new(); // Reading in input from human println!("Please enter the important information we need:"); match std::io::stdin().read_line(&mut input) { Ok(_) => (), Err(error) => panic!("Problem reading in input: {:?}", error), } // Matching the input let val = match parse_string_tuple(input.trim()) { Some(i) => i, None => 0, }; // Make move player_red.make_move(val); // Tell other thread that reading in is finished and important calculations should be stopped tx_blue.send(0).unwrap(); }); manager_red.join().unwrap(); manager_blue.join().unwrap(); let counter = rx_blue_back.recv().unwrap(); println!("Counter is: {}", counter); } println!("Finished"); } fn parse_string_tuple(string: &str) -> Option<u32> { if let Ok(i) = string.parse() { Some(i) } else { None } }
编译错误信息
加入游戏回合循环后,Engine实例会被移动到线程闭包中,导致后续迭代无法复用,出现如下编译错误:
error[E0382]: use of moved value: `player_blue` --> src/main.rs:27:42 | 20 | let mut player_blue = Engine::new(); | --------------- move occurs because `player_blue` has type `Engine`, which does not implement the `Copy` trait ... 27 | let manager_blue = thread::spawn(move || { | ^^^^^^^ value moved into closure here, in previous iteration of loop ... 31 | player_blue.make_move(t); | ----------- use occurs due to use in closure
现请教如何解决该所有权问题,是否可以从线程中回收Engine实例,或是有其他更合适的实现方案?
核心思路:通过所有权转移回收或共享访问解决实例复用问题
在Rust中,move闭包会捕获变量的所有权并转移到线程中,线程结束后这些变量默认不会自动返回。针对你的场景,有两种常用的解决思路:
方案一:通过通道将Engine实例从线程返回
既然需要复用player_blue,可以在线程完成任务后,将player_blue通过额外的通道发送回主线程,这样每次循环结束后主线程就能重新获得实例的所有权。
修改后的关键代码如下:
fn main() { let mut player_blue = Engine::new(); let mut player_red = Engine::new(); for _ in 0..2 { let (tx_blue, rx_blue) = mpsc::channel(); let (tx_blue_back, rx_blue_back) = mpsc::channel(); // 新增通道用于返回Engine实例 let (tx_engine_return, rx_engine_return) = mpsc::channel(); let manager_blue = thread::spawn(move || { let mut counter = 0; loop { if let Ok(t) = rx_blue.try_recv() { player_blue.make_move(t); tx_blue_back.send(counter).unwrap(); // 将Engine实例发送回主线程 tx_engine_return.send(player_blue).unwrap(); break; } counter += 1; } }); let manager_red = thread::spawn(move || { let mut input = String::new(); println!("Please enter the important information we need:"); match std::io::stdin().read_line(&mut input) { Ok(_) => (), Err(error) => panic!("Problem reading in input: {:?}", error), } let val = match parse_string_tuple(input.trim()) { Some(i) => i, None => 0, }; player_red.make_move(val); tx_blue.send(0).unwrap(); }); manager_red.join().unwrap(); manager_blue.join().unwrap(); let counter = rx_blue_back.recv().unwrap(); // 回收Engine实例 player_blue = rx_engine_return.recv().unwrap(); println!("Counter is: {}", counter); } println!("Finished"); }
这个方案完全遵循Rust的所有权规则,没有额外的同步开销,适合你的场景——每次循环中AI线程只需要独占访问Engine实例。
方案二:使用智能指针实现共享可变访问
如果需要更灵活的线程间共享(比如多个线程同时访问Engine),可以使用Arc<Mutex<Engine>>或Arc<RwLock<Engine>>来包装实例。Arc提供原子引用计数实现共享所有权,Mutex保证同一时间只有一个线程能可变访问实例。
修改后的关键代码如下:
use std::sync::{mpsc, Arc, Mutex}; // ... 其余代码不变 fn main() { // 用Arc<Mutex>包装Engine实例 let player_blue = Arc::new(Mutex::new(Engine::new())); let mut player_red = Engine::new(); for _ in 0..2 { let (tx_blue, rx_blue) = mpsc::channel(); let (tx_blue_back, rx_blue_back) = mpsc::channel(); // 克隆Arc,增加引用计数 let blue_clone = Arc::clone(&player_blue); let manager_blue = thread::spawn(move || { let mut counter = 0; loop { if let Ok(t) = rx_blue.try_recv() { // 锁定Mutex获得可变引用 let mut engine = blue_clone.lock().unwrap(); engine.make_move(t); tx_blue_back.send(counter).unwrap(); break; } counter += 1; } }); // ... 其余代码不变,player_red的处理同理如果需要共享的话 manager_red.join().unwrap(); manager_blue.join().unwrap(); let counter = rx_blue_back.recv().unwrap(); println!("Counter is: {}", counter); } println!("Finished"); }
这个方案适合需要多线程频繁访问实例的场景,但会带来一定的同步锁开销。对于你的四子棋AI场景,方案一已经足够简洁高效。
额外优化建议
- 对于蒙特卡洛AI的计算,可以让AI线程在收到终止信号前持续迭代优化最佳落子,而非简单计数,充分利用人类玩家思考的时间。
- 可以用
select!宏替代try_recv()的循环,让代码更简洁高效,避免空循环浪费CPU资源。
内容的提问来源于stack exchange,提问作者Raoul Luqué

