




























Rust 的线程模型基于底层操作系统的 1:1 线程模型,这意味着一个系统线程直接对应一个 Rust 线程。Rust 通过其独创的所有权(Ownership)和借用(Borrowing)机制,在编译期就能杜绝并发编程中最头疼的“数据竞争”问题。
在 Rust 中,使用标准库的 std::thread::spawn 可以轻松创建一个新线程。
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(1));
}
});
for i in 1..3 {
println!("主线程: {}", i);
thread::sleep(Duration::from_millis(1));
}
// join() 会阻塞当前线程,直到 handle 对应的线程执行完毕
handle.join().unwrap();
}
join() 方法:通过调用 handle.join(),主线程会等待子线程运行结束,确保任务完整执行。1). 为什么 handle.join() 后面必须加 .unwrap()?
在 Rust 中,handle.join() 返回的不是普通的 Result<T, E>,而是一个特殊的类型:Result<T, Box<dyn Any + Send + 'static>> [1]
当子线程运行结束时,主线程通过 join() 会收到两种完全不同的执行结果。加 .unwrap() 的本质,是为了在子线程发生崩溃(Panic)时,让主线程也跟着一起崩溃,防止错误被无意中掩盖。
我们可以把这两类结果拆解来看:
.unwrap() 会直接解包并返回子线程闭包中的返回值 T [1]。.unwrap() 去解包,主线程就会拿到这个 Err 并继续若无其事地往下走,这在大多数并发业务中是非常危险的。通过 .unwrap(),一旦子线程崩溃,主线程在解包时也会立即触发 Panic(即崩溃传递),确保系统能够及时暴露问题。线程状态
1). Joinable(可连接状态)
在 Rust 中,通过 std::thread::spawn 创建的线程默认都是 Joinable 的。
特点
.join(),这些资源才会被彻底清空。.join() 的线程。Rust 中的表现:JoinHandle
thread::spawn 会返回一个名为 JoinHandle<T> 的结构体。它就是操作该线程的“把手”。
2). Detached(分离状态)
分离状态意味着线程与创建它的父线程“断开”了联系。一旦线程被分离,它将完全脱离宿主线程的控制,由操作系统独立管理。
特点
.join(),也无法得知它何时结束。Rust 中如何创建分离线程?
在 Rust 中,你不需要显式调用类似 detach() 的函数。Rust 巧妙地利用了所有权系统(Drop 机制)来实现分离:
规矩:如果你让
JoinHandle离开了作用域(被销毁/Drop),且没有调用.join(),那么这个线程在底层就会被隐式地转为分离(Detached)状态。
move 闭包当你想在子线程中使用主线程的变量时,普通的闭包会因为“借用检查”而报错(因为 Rust 无法确定主线程的变量能活得比子线程久)。此时必须使用 move 关键字。
use std::thread;
fn main() {
let v = vec![1, 2, 3];
// 使用 move 关键字将 v 的所有权强制转移(move)到新线程中
let handle = thread::spawn(move || {
println!("子线程使用 vector: {:?}", v);
});
// 此处不能再使用 v,因为所有权已经没了
// println!("{:?}", v); // 编译报错!
handle.join().unwrap();
}
Rust 标准库提供了 mpsc(Multi-producer, Single-consumer,多生产者,单消费者)通道,专门用于线程间传递数据。
use std::sync::mpsc;
use std::thread;
fn main() {
// 创建通道,tx 是发送端(Sender),rx 是接收端(Receiver)
let (tx, rx) = mpsc::channel();
// 克隆一个发送端,用于多个生产者
let tx1 = tx.clone();
// 生产者线程 1
thread::spawn(move || {
tx1.send(String::from("来自线程 1 的问候")).unwrap();
});
// 生产者线程 2
thread::spawn(move || {
tx.send(String::from("来自线程 2 的问候")).unwrap();
});
// 消费者(主线程)接收数据
for received in rx {
println!("收到: {}", received);
}
}
send 方法会转移数据的所有权,防止发送后数据被修改引发混乱。tx)都被释放时,迭代器 rx 会自动结束循环。Arc 与 Mutex如果你需要多个线程同时读写同一块内存数据,通道就不适用了。Rust 使用原子引用计数(Arc)和互斥锁(Mutex)来实现安全的共享内存。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Mutex 提供互斥访问,Arc 提供跨线程的多重所有权
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 || {
// lock() 获取锁,如果锁被占用则等待。
// lock_num 是一个智能指针(MutexGuard),离开作用域时自动解锁!
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终计数结果: {}", *counter.lock().unwrap());
}
Send 与 Sync 特征Rust 的并发安全并不是魔法,而是基于类型系统的两个内置特质(Trait):
Send:表明该类型的数据可以转移到其他线程(绝大多数类型都是 Send,但 Rc<T> 不是)。Sync:表明该类型的数据可以被多个线程同时通过引用传递(如果 T 是 Sync,那么 &T 就是 Send)。只要你在编写并发代码时违反了这两个特质,编译器就会直接拒绝编译,从而在源头上杜绝了运行时的并发 Bug。
参考资料:
《Rust 权威指南》
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。