第5章 - 并发编程
嗨,朋友!我是长安。
恭喜你来到了进阶教程的最后一章!这一章我们要学习 Rust 最强大的特性之一——并发编程。
Rust 的类型系统和所有权系统让你可以写出安全的并发代码,大部分并发错误都能在编译时被捕获!
🤔 什么是并发?
并发(Concurrency)是指程序的不同部分独立执行。
并行(Parallelism)是指程序的不同部分同时执行。
长安说
想象你在做饭:
- 并发:你一会儿切菜,一会儿烧水,交替进行
- 并行:你和朋友同时做饭,你切菜,朋友烧水
Rust 的并发特性同时支持这两种场景!
🧵 创建线程
基本用法
使用 thread::spawn 创建新线程:
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..=5 {
println!("子线程: {}", i);
thread::sleep(Duration::from_millis(500));
}
});
for i in 1..=3 {
println!("主线程: {}", i);
thread::sleep(Duration::from_millis(300));
}
}
注意
上面的代码可能不会打印完全部子线程的输出,因为主线程结束时会关闭所有子线程!
等待线程完成
使用 join 等待线程执行完毕:
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(500));
}
});
for i in 1..=3 {
println!("主线程: {}", i);
thread::sleep(Duration::from_millis(300));
}
handle.join().unwrap(); // 等待子线程完成
println!("所有线程完成!");
}
📦 线程间传递数据
使用 move 闭包
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("向量: {:?}", v);
});
// println!("{:?}", v); // ❌ 错误!v 的所有权已转移
handle.join().unwrap();
}
move 关键字强制闭包获取环境变量的所有权。
📬 消息传递
Rust 提供了通道(Channel)来实现线程间通信。
创建通道
use std::sync::mpsc; // multiple producer, single consumer
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("你好");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("收到: {}", received);
}
tx(transmitter):发送者rx(receiver):接收者send:发送消息recv:接收消息(阻塞)
发送多个消息
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let messages = vec![
String::from("消恗1"),
String::from("消恗2"),
String::from("消恗3"),
];
for msg in messages {
tx.send(msg).unwrap();
thread::sleep(Duration::from_millis(500));
}
});
for received in rx {
println!("收到: {}", received);
}
}
多个生产者
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone(); // 克隆发送者
thread::spawn(move || {
tx.send(String::from("线程1")).unwrap();
});
thread::spawn(move || {
tx1.send(String::from("线程2")).unwrap();
});
for received in rx {
println!("{}", received);
}
}
🔒 共享状态
Mutex - 互斥锁
Mutex<T> 允许多个线程访问同一个数据,但同一时间只有一个线程可以修改。
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
} // 锁在这里自动释放
println!("m = {:?}", m);
}
Arc - 原子引用计数
还记得 Rc<T> 吗?它只能用于单线程。多线程要用 Arc<T>(Atomic Reference Counted)。
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());
}
长安说
Arc<Mutex<T>> 是 Rust 中多线程共享可变数据的标准模式!
Arc:多个线程拥有所有权Mutex:保证同一时间只有一个线程修改
🎯 实战例子:并发计数器
use std::sync::{Arc, Mutex};
use std::thread;
#[derive(Debug)]
struct Counter {
count: Mutex<i32>,
}
impl Counter {
fn new() -> Self {
Counter {
count: Mutex::new(0),
}
}
fn increment(&self) {
let mut count = self.count.lock().unwrap();
*count += 1;
}
fn get(&self) -> i32 {
*self.count.lock().unwrap()
}
}
fn main() {
let counter = Arc::new(Counter::new());
let mut handles = vec![];
for _ in 0..100 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
counter.increment();
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终计数: {}", counter.get());
}
🚀 并行计算示例
让我们用多线程计算一个向量的和:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let numbers: Vec<i32> = (1..=1000).collect();
let sum = Arc::new(Mutex::new(0));
let mut handles = vec![];
let chunk_size = 250;
for chunk in numbers.chunks(chunk_size) {
let sum = Arc::clone(&sum);
let chunk = chunk.to_vec();
let handle = thread::spawn(move || {
let local_sum: i32 = chunk.iter().sum();
let mut total = sum.lock().unwrap();
*total += local_sum;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("1到1000的和: {}", *sum.lock().unwrap());
}
🚨 Send 和 Sync Trait
Rust 的并发安全来自两个特殊的 Trait:
Send
Send trait 表示类型的所有权可以在线程间转移。
几乎所有 Rust 类型都实现了 Send,但 Rc<T> 没有!
Sync
Sync trait 表示类型可以安全地被多个线程引用。
如果 &T 实现了 Send,那么 T 就实现了 Sync。
长安说
你几乎不需要手动实现 Send 和 Sync。Rust 会自动为你的类型实现它们(如果安全)。
如果你的类型不安全,编译器会阻止你在多线程中使用!
📊 并发模式对比
| 模式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 消息传递 | 线程间需要通信 | 安全,无锁 | 需要复制数据 |
| 共享状态 | 需要修改同一数据 | 高效,无需复制 | 需要加锁 |
💡 小结
在这一章,我们学会了:
- 使用
thread::spawn创建线程 - 使用
join等待线程完成 - 使用 通道(
mpsc::channel)实现消息传递 - 使用
Mutex<T>实现共享状态 - 使用
Arc<T>在线程间共享所有权 Arc<Mutex<T>>是多线程共享可变数据的标准模式Send和Synctrait 保证并发安全
核心要点:
- Rust 的类型系统在编译时防止数据竞争
- 消息传递和共享状态是两种主要并发模式
Arc用于多线程,Rc用于单线程Mutex保证数据同时只被一个线程访问
长安的建议
并发编程在其他语言中通常很难,但在 Rust 中相对简单!因为:
- 编译器帮你检查:大部分并发错误都能在编译时发现
- 模式清晰:消息传递和共享状态两种模式
- 类型安全:
Send和Sync自动保证安全
建议:
- 优先使用消息传递(更安全)
- 必须时才用共享状态
- 用
Arc<Mutex<T>>处理共享可变数据
🎉 恭喜你!
你已经完成了整个 Rust 教程!
从第一章认识 Rust,到最后掌握并发编程,你已经掌握了 Rust 的核心概念:
✅ 所有权系统
✅ 引用与借用
✅ 结构体与枚举
✅ 泛型与 Trait
✅ 生命周期
✅ 智能指针
✅ 并发编程
接下来,你可以:
- 做项目:把学到的知识应用到实际项目中
- 阅读代码:阅读优秀的 Rust 项目代码
- 深入学习:学习 async/await、宏系统等高级特性
- 加入社区:参与 Rust 开源社区
记住:Rust 的学习曲线比较陡,但一旦掌握,你会发现它真的非常优雅和强大!
继续努力,用 Rust 创造更多精彩的项目吧!🚀
—— 长安
💪 练习题
- 创建 5 个线程,每个线程打印自己的 ID
- 使用通道让主线程收集所有子线程的结果
- 用
Arc<Mutex<Vec<i32>>>创建一个多线程共享的列表,多个线程向其中添加数据 - 实现一个多线程的文件读取器,将文件分成多个块并行处理
答案示例
use std::sync::{Arc, Mutex, mpsc};
use std::thread;
// 练习1
fn exercise1() {
let mut handles = vec![];
for i in 0..5 {
let handle = thread::spawn(move || {
println!("线程 ID: {}", i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
// 练习2
fn exercise2() {
let (tx, rx) = mpsc::channel();
let mut handles = vec![];
for i in 0..5 {
let tx = tx.clone();
let handle = thread::spawn(move || {
tx.send(i * i).unwrap();
});
handles.push(handle);
}
drop(tx); // 关闭原始发送者
for handle in handles {
handle.join().unwrap();
}
for received in rx {
println!("收到: {}", received);
}
}
// 练习3
fn exercise3() {
let list = Arc::new(Mutex::new(Vec::new()));
let mut handles = vec![];
for i in 0..10 {
let list = Arc::clone(&list);
let handle = thread::spawn(move || {
let mut l = list.lock().unwrap();
l.push(i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("列表: {:?}", *list.lock().unwrap());
}
fn main() {
println!("=== 练习1 ===");
exercise1();
println!("\n=== 练习2 ===");
exercise2();
println!("\n=== 练习3 ===");
exercise3();
}
