在单核时代,线程是奢侈品;如今多核 CPU 遍地,线程是标配。但是并发编程的坑太多——数据竞争、死锁、内存泄漏……Rust 以其“零成本抽象”和铁腕借用检查器,化险为夷,让你写出安全、高效的多线程代码。
1. Rust 线程基础:spawn、join 和 move 闭包Rust 的线程在 std::thread 模块中。最简单的方式:用 thread::spawn 创建线程,返回一个 JoinHandle,用 join 等待结果。入门代码:Hello World 多线程版
use std::thread;
use std::time::Duration;
fn main() {
// 创建一个线程,打印消息
let handle = thread::spawn(|| {
for i in 1..=5 {
println!("线程 {}: 嗨,从线程来!", i);
thread::sleep(Duration::from_millis(100)); // 模拟工作
}
});
// 主线程也干活
for i in 1..=3 {
println!("主线程 {}: 我在忙呢!", i);
thread::sleep(Duration::from_millis(200));
}
// 等待子线程结束
handle.join().unwrap();
println!("所有线程完成!");
}运行后,你会看到主线程和子线程交替输出。spawn 的闭包必须是 FnOnce,因为线程启动后就“消费”了它。如果闭包捕获外部变量,默认借用会报错(线程生命周期独立),所以用 move 关键字移动所有权。小 tip:join 阻塞主线程,直到子线程结束。忽略它,程序可能提前退出。2. 实际案例一:并行计算斐波那契数列斐波那契(Fib)是经典递归问题,单线程慢如狗。用线程并行计算大 Fib 值,性能飞起!场景:批量计算 Fib(30)、Fib(31)、Fib(32)单线程版太慢,我们用线程池(std::thread 内置)并行。
use std::thread;
fn fib(n: u64) -> u64 {
if n <= 1 { n } else { fib(n-1) + fib(n-2) } // 简单递归,实际用 memoization
}
fn main() {
let handles: Vec<_> = (30..=32)
.map(|n| {
thread::spawn(move || { // move 捕获 n
let res = fib(n);
println!("Fib({}) = {}", n, res);
res
})
})
.collect();
for handle in handles {
let result = handle.join().unwrap();
println!("线程返回: {}", result);
}
}输出类似:
Fib(30) = 832040
Fib(31) = 1346269
Fib(32) = 2178309
线程返回: 832040
...为什么会快?因为 每个线程独立计算,不共享状态。实际项目中,用 rayon 库(Cargo add rayon)简化:let results: Vec<_> = (30..=32).into_par_iter().map(fib).collect(); —— 零 boilerplate!性能对比:单线程 ~5s,多线程 ~2s(视 CPU 核数)。在数据科学工具中,这能加速矩阵运算或模拟。3. 线程通信:通道(Channels)入门线程间不共享数据?用 std::sync::mpsc(multiple producer, single consumer)通道传递消息。像邮局:发送方 sender,接收方 receiver。案例:生产者-消费者模式模拟日志系统:主线程生产日志,子线程消费并打印。
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel(); // 创建通道
// 生产者线程
let producer = thread::spawn(move || {
for i in 1..=5 {
let log = format!("日志 {}: 系统正常", i);
tx.send(log).unwrap(); // 发送消息
thread::sleep(Duration::from_millis(200));
}
});
// 消费者(主线程)
for received in rx { // 阻塞接收,直到通道关闭
println!("收到: {}", received);
if received.contains("5") { break; }
}
producer.join().unwrap();
}输出:
收到: 日志 1: 系统正常
收到: 日志 2: 系统正常
...扩展:多生产者用 mpsc::sync_channel。在 Web 爬虫中,线程抓取页面,主线程汇总结果,避免阻塞。4. 共享数据:Arc + Mutex 安全访问Rust 不允许直接共享可变数据(防数据竞争)。用 Arc(原子引用计数)共享所有权,Mutex(互斥锁)保护访问。实际案例:多线程计数器银行转账模拟:多个线程并发存款,共享余额。
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 { // 10 个线程各加 100
let counter = Arc::clone(&counter); // 克隆 Arc
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap(); // 加锁
*num += 100;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终余额: {}", *counter.lock().unwrap()); // 1000
}为什么安全?Mutex::lock 确保同一时间只有一个线程修改。Arc 管理引用计数,线程结束自动释放。
实战场景:在游戏服务器中,Arc<Mutex<Player>> 共享玩家状态;或 CLI 工具并行下载文件,Mutex 记录进度条。警告:死锁风险!别在锁内调用另一个锁住的 Mutex。用 RwLock 优化读多写少场景。5. 高级技巧与常见坑
调试神器:RUST_LOG=debug cargo run,或 gdb 附着。线程,让 Rust 并发如丝般顺滑
Rust 线程不是“可选糖衣”,而是安全并发的基石。从 spawn 一个简单任务,到 Arc 守护共享状态,你已掌握 80% 技能。实战中,从小项目起步:试试并行排序日志文件,或构建 mini Web 服务器。Rust 的并发哲学:让编译器抓错,别让运行时崩溃。