Rust 学习笔记 21:并发编程 (无畏并发)
“Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.” – Rob Pike
在很多语言中,并发编程是噩梦。数据竞争 (Data Race)、死锁 (Deadlock)、竞态条件 (Race Condition) 让人防不胜防。 Rust 号称 “Fearless Concurrency”(无畏并发)。它利用所有权和类型系统,在编译期就把数据竞争扼杀在摇篮里。
1. 使用线程
Rust 默认使用 1:1 线程模型,即一个 Rust 线程对应一个操作系统线程。
1use std::thread;
2use std::time::Duration;
3
4fn main() {
5 thread::spawn(|| {
6 for i in 1..10 {
7 println!("hi number {} from the spawned thread!", i);
8 thread::sleep(Duration::from_millis(1));
9 }
10 });
11
12 for i in 1..5 {
13 println!("hi number {} from the main thread!", i);
14 thread::sleep(Duration::from_millis(1));
15 }
16}
注意:当主线程结束时,所有派生线程都会被强制终止,无论它们是否执行完。
2. Join Handles
为了让主线程等待子线程结束,我们需要保存 thread::spawn 的返回值(JoinHandle),并调用 join() 方法。
1let handle = thread::spawn(|| { ... });
2// ... 做点别的 ...
3handle.join().unwrap();
3. 线程与 move 闭包
如果在线程中使用了外部变量,必须小心所有权问题。
1let v = vec![1, 2, 3];
2
3let handle = thread::spawn(|| {
4 println!("Here's a vector: {:?}", v); // 报错!
5});
Rust 编译器会报错:由于线程可能活得比主线程长,它不能借用 v。必须使用 move 关键字强制把 v 的所有权移动到线程闭包中。
1let handle = thread::spawn(move || {
2 println!("Here's a vector: {:?}", v);
3});
一旦移入,主线程就不能再使用 v 了。这就是 Rust 保证内存安全的方式:同一时间只有一个所有者。
4. 小结
第21篇笔记。我们迈出了并发编程的第一步。
Go 语言有 Goroutine (M:N 模型),Rust 选择了原生线程 (1:1 模型) 作为标准库实现,但通过 async/await (在后续章节) 也支持类似 Goroutine 的轻量级并发。
但无论哪种模型,Rust 的所有权规则都适用。
下一篇,我们将探讨 共享状态并发 (Shared-State Concurrency)。既然 Rust 不让多线程同时拥有一个变量,那我们怎么共享数据呢?Mutex 和 Arc 将闪亮登场。
练习题:
- 尝试在不使用
move的情况下,在子线程引用主线程的变量,观察编译器的报错信息。 - 创建 10 个线程,每个线程通过
join互相等待(类似于接力赛)。
思考题:
为什么 Rust 标准库选择 1:1 线程模型而不是像 Go 那样的 M:N 模型?这对系统编程语言意味着什么?(提示:Runtime 大小、FFI 交互)。
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
