线程、通道、共享状态与锁
本章目标
这一章学习 Rust 并发基础:线程如何启动,消息如何在线程间传递,共享状态为什么需要锁,以及 Rust 如何在编译期阻止数据竞争。
它是什么
线程是操作系统调度的执行单元。多个线程可以同时运行,让程序处理多个任务。
Rust 标准库提供:
std::thread:创建线程。std::sync::mpsc:多生产者单消费者通道。std::sync::Mutex:互斥锁。std::sync::Arc:原子引用计数,用于多线程共享所有权。
为什么需要
当前 RinBlog 为了教学简单,一次只处理一个连接。真实服务器通常需要同时处理多个请求。并发能提升吞吐,但也会带来共享数据修改、锁等待和死锁等问题。
Rust 的类型系统要求跨线程数据满足安全约束。你不能随便把非线程安全引用丢给另一个线程。
怎么使用
启动线程:
let handle = std::thread::spawn(|| {
println!("worker thread");
});
handle.join().unwrap();
共享计数器:
use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));
let shared = Arc::clone(&counter);
let handle = std::thread::spawn(move || {
let mut value = shared.lock().unwrap();
*value += 1;
});
handle.join().unwrap();
逐行解释
let counter = Arc::new(Mutex::new(0));
0是被保护的整数。Mutex::new(0)创建互斥锁,任何时刻只允许一个线程访问内部整数。Arc::new(...)创建线程安全引用计数指针。- 多个线程需要共同拥有同一个锁,所以要用
Arc。
let mut value = shared.lock().unwrap();
*value += 1;
lock()尝试获取锁。- 成功后返回守卫对象,守卫离开作用域时自动释放锁。
unwrap()在锁中毒时 panic,教学代码可以这样写,工程代码应认真处理。*value += 1修改锁内部的整数。
常见坑
Rc不能跨线程共享,要用Arc。RefCell不是线程锁,多线程共享要用Mutex或RwLock。- 锁持有时间越短越好,不要在持锁时做慢 IO。
- 通道适合传递消息,锁适合保护共享状态。先选模型,再写代码。
练习
- 启动一个线程打印一句话。
- 启动三个线程,每个线程发送一个字符串到通道。
- 用
Arc<Mutex<i32>>实现多线程计数。 - 故意用
Rc<Mutex<i32>>跨线程,阅读编译错误。
造轮子任务
设计一个迷你线程池接口:ThreadPool::new(size) 和 execute(job)。先写类型和伪实现,理解任务队列、工作线程、通道三者关系。后续可以把它接到 HTTP 服务器上。
小结
Rust 并发不是没有复杂度,而是把复杂度显式化。你要清楚数据是在线程间移动、通过通道传递,还是通过锁共享。类型系统会帮助你避免数据竞争。