线程基础
问题
Rust 如何创建和管理线程?为什么线程闭包需要 move?
答案
创建线程
use std::thread;
use std::time::Duration;
fn main() {
// 创建线程,返回 JoinHandle
let handle = thread::spawn(|| {
for i in 0..5 {
println!("子线程: {}", i);
thread::sleep(Duration::from_millis(100));
}
42 // 线程返回值
});
println!("主线程继续执行");
// 等待线程结束,获取返回值
let result = handle.join().unwrap(); // 42
println!("线程结果: {}", result);
}
move 闭包
线程可能比创建它的作用域活得更久,所以必须拥有捕获的数据:
fn main() {
let name = String::from("Rust");
// ❌ 编译错误:闭包可能比 name 活得久
// let handle = thread::spawn(|| {
// println!("{}", name);
// });
// ✅ move 将 name 的所有权转移到闭包中
let handle = thread::spawn(move || {
println!("Hello, {}!", name);
});
// name 已被移动,这里不能再用
handle.join().unwrap();
}
线程配置
let handle = thread::Builder::new()
.name("worker-1".into()) // 线程名
.stack_size(4 * 1024 * 1024) // 栈大小 4MB
.spawn(|| {
println!("线程: {:?}", thread::current().name());
})
.expect("创建线程失败");
多线程共享数据
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("结果: {}", *counter.lock().unwrap()); // 10
}
作用域线程(Scoped Threads)
Rust 1.63+ 的 thread::scope 允许线程借用局部变量而不需要 move:
fn main() {
let mut data = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| {
println!("读取: {:?}", &data); // ✅ 可以借用
});
s.spawn(|| {
println!("也能读: {:?}", &data);
});
}); // 作用域结束自动 join 所有线程
data.push(4); // ✅ 线程已结束,可以继续使用
}
常见面试问题
Q1: thread::spawn 要求闭包满足什么条件?
答案:
闭包必须满足 Send + 'static:
Send:闭包捕获的所有数据都可以安全跨线程传递'static:捕获的数据不包含非静态引用(因为线程可能比调用者活得更久)
这就是为什么通常需要 move 或使用 Arc 共享数据。
Q2: join() 返回什么?线程 panic 会怎样?
答案:
join() 返回 Result<T, Box<dyn Any + Send>>:
Ok(value)— 线程正常结束Err(panic_info)— 线程发生了 panic
线程 panic 不会影响其他线程。主线程可以通过 join() 捕获并处理。
Q3: Scoped Threads 相比普通线程有什么优势?
答案:
- 不需要
Arc包装共享数据,直接借用局部变量 - 不需要
move,减少所有权转移的开销 - 作用域结束自动 join,不会遗漏
- 编译器能保证线程不会越过作用域存活