跳到主要内容

线程基础

问题

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,不会遗漏
  • 编译器能保证线程不会越过作用域存活

相关链接