章节目录

线程、通道、共享状态与锁

本章目标

这一章学习 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 不是线程锁,多线程共享要用 MutexRwLock
  • 锁持有时间越短越好,不要在持锁时做慢 IO。
  • 通道适合传递消息,锁适合保护共享状态。先选模型,再写代码。

练习

  1. 启动一个线程打印一句话。
  2. 启动三个线程,每个线程发送一个字符串到通道。
  3. Arc<Mutex<i32>> 实现多线程计数。
  4. 故意用 Rc<Mutex<i32>> 跨线程,阅读编译错误。

造轮子任务

设计一个迷你线程池接口:ThreadPool::new(size)execute(job)。先写类型和伪实现,理解任务队列、工作线程、通道三者关系。后续可以把它接到 HTTP 服务器上。

小结

Rust 并发不是没有复杂度,而是把复杂度显式化。你要清楚数据是在线程间移动、通过通道传递,还是通过锁共享。类型系统会帮助你避免数据竞争。