本章要回答的三个问题:开线程时数据的产权怎么移交?线程间通信选消息传递还是共享内存?Send 与 Sync 两条标记契约凭什么把数据竞争挡在编译期?
第 1 章卷宗三(数据竞争)在这里终审。多数语言靠运行时锁检查或纯粹靠程序员自觉,Rust 的判法还是老一套:把权限写进类型。线程闭包要拿数据所有权,跨线程共享要先过 Send 与 Sync 的资格审查,锁的守卫生命周期与借用规则绑定——忘了解锁不是 bug,是编译错误。
| 节 | 回答哪个问题 | 关键产出 |
|---|---|---|
| 8.1 线程 | 产权怎么进线程 | move 闭包、join 等待 |
| 8.2 消息传递 | 所有权流转式通信 | mpsc 通道、clone 共享发送端 |
| 8.3 共享状态 | 共享加锁怎么写 | Mutex、Arc、Send 与 Sync |
第 11 章异步编程是并发的另一条路线:少量线程跑海量任务,与本章的 OS 线程路线互补。
| 报错 | 案情 | 修法方向 |
|---|---|---|
| E0373 | 闭包借用环境去 spawn | 加 move 捕获 |
| E0277 | 非 Send 类型跨线程 | Rc→Arc、RefCell→Mutex |
| E0382 | move 后原变量使用 | clone 或 Arc 共享 |
| 挂起无报错 | 死锁 | 锁全序、缩小临界区 |
| 维度 | 消息传递 | 共享状态 |
|---|---|---|
| 产权 | 随消息移交 | 锁内借用 |
| 适合 | 流水线、任务分发 | 计数器、缓存、配置 |
| 陷阱 | 无界通道内存涨 | 死锁、锁粒度过大 |
| 心智负担 | 所有权自然分割 | 需要额外纪律 |
Go 阵营"不要靠共享内存来通信"的口号在 Rust 里被编译器执行成法条:两条路线都安全,选型看数据流向而非口味。单向流动优先通道,多点读写同一状态才上锁。
并发代码高度重复的模式——起 N 个 worker、注册一堆处理器、生成样板测试——正是宏的主场。第 9 章的声明宏会直接把本章 worker 池的样板宏化,作为学以致用的判例。
并发代码提交前过一遍:每个 spawn 的闭包捕获方式(move 与否)是否经过选择而非顺手;每个 join 的 Err 分支是否有处理;锁的获取顺序是否遵守全局全序;通道是否有界或已论证无需界;共享可变数据是否全部经过 Arc 包装而无一裸奔。五问全过,数据竞争在编译期与设计期就已被双重拦截——这正是 Rust 并发的承诺:安全靠编译器,正确靠纪律。
一问:为什么 Rc 不能跨线程而 Arc 可以?答:Rc 计数非原子,并发更新会算错并双重释放;Arc 用原子计数。二问:死锁的鉴别特征是什么?答:线程挂起、CPU 空闲、无 panic 无报错。三问:无界 channel 的风险?答:生产快于消费时内存无上限增长,需换 sync_channel 定容。三题过关,并发庭的两大路线——传递与共享——就已把各自的坑位地图画全。
并发庭三节合起来是一份风险清单:起线程审捕获方式(第 8.1 节)、传消息审通道容量与关闭(第 8.2 节)、共状态审锁序与中毒(第 8.3 节)。三份清单之外再记一条总则:并发 bug 的复现概率与线程数成反比,能在两线程、加断言、固定节奏的测试里复现的问题不要留到生产环境碰运气。Rust 把数据竞争挡在编译期,逻辑层面的死锁与饥饿仍属设计与测试的辖区——这份分工认识本身,就是并发章最重要的产出。