第3章 Rust并发编程:线程、消息传递、互斥锁、async/await 模型

📌 本章聚焦大厂高频并发面试题,拆解 Rust 四种并发模型的适用场景与回答框架。重点掌握:线程 vs async 选择依据、Mutex 与消息传递的取舍、Send/Sync 特性含义。
--- ### 核心概念图:Rust 并发编程回答框架 并发编程问题 std::thread CPU密集 / 简单并行 消息传递 (mpsc) actor 模型 / 解耦 Mutex / RwLock 共享状态 async/await IO密集 / 高并发连接 根据场景选择,避免过度抽象

▶ 面试回答路径:先明确问题类型 → 对比四种方案 → 给出选择理由 → 举例说明实现方式。

--- ### 面试问答卡片 #### 卡片1:线程与 async/await 的选择
面试问题:“在 Rust 中,你什么时候使用线程(std::thread),什么时候使用 async/await?”
| 考察点 | 回答框架 | 示例回答 | |---|---|---| | 区分 CPU 密集 vs IO 密集任务;理解 async 运行时开销;掌握 Send/Sync 制约 | ① 任务类型分类 → ② 线程适合 CPU 并行,async 适合高 IO 等待 → ③ 对比上下文切换开销 → ④ 结合项目举例 → ⑤ 注意 `.await` 只能在 async 函数中使用 | 线程用于计算密集型并行(如图像处理),每核一个线程;async 用于 web 服务器处理数千连接(如 tokio+hyper),利用少量线程调度大量任务。若任务无 IO 等待,线程更直接;await 点过多时需注意阻塞风险。 |
可能追问 + 避坑提醒
  • 追问:tokio 的 work-stealing 调度器原理?如何避免 async 函数中的阻塞?
  • 避坑:不要将 Mutex::lock() 跨 .await 持有(导致死锁);CPU 密集任务不要放在 async 执行器里,否则会饿死其他任务。
--- #### 卡片2:消息传递 vs 共享状态(Mutex)
面试问题:“比较 Rust 中通过 mpsc 消息传递和通过 Arc<Mutex<T>> 共享状态两种并发方式,你如何选择?”
| 考察点 | 回答框架 | 示例回答 | |---|---|---| | 理解 Go 名言“不要通过共享内存来通信”;识别死锁风险;性能对比 | ① 消息传递解耦,避免数据竞争 → ② 共享状态适合高频小数据更新 → ③ 单消费者/multi-producer 场景 → ④ 考虑 Mutex 粒度(细粒度 vs 粗粒度) → ⑤ 可测试性 | WebSocket 聊天室使用 mpsc-per-connection 实现广播;统计计数器使用 Arc<AtomicU64>(无锁共享);高性能缓存使用 RwLock<HashMap>(读多写少)。消息传递增加拷贝开销,共享状态需警惕死锁。 |
可能追问 + 避坑提醒
  • 追问:mpsc 的发送端是 Sync 吗?如何实现多生产者?
  • 避坑:MutexGuard 生命周期不跨越 .await;RwLock 写锁会阻塞所有读锁,实时性要求高时慎用。
--- #### 卡片3:Send 和 Sync 特性
面试问题:“解释 Rust 中的 Send 和 Sync trait,以及如何影响并发编程?”
| 考察点 | 回答框架 | 示例回答 | |---|---|---| | 理解所有权与并发安全;编译器静态检查原理;实现自定义类型时如何标记 | ① Send 允许所有权跨线程转移 → ② Sync 允许共享引用跨线程 → ③ 多数基本类型自动实现 → ④ UnsafeCell 破坏 Sync → ⑤ Rc 未实现 Send | 例如 Arc<T> 要求 T: Send + Sync 才能在线程间共享。自定义的 MyCounter 如果内部有 Cell,则需手动标记 unsafe impl Sync。编译器在跨线程边界时自动推导,失败则报错。 |
可能追问 + 避坑提醒
  • 追问:裸指针 *const T 默认 Send 吗?如何安全包装?
  • 避坑:不要手动实现 Send/Sync 除非清楚内存模型;避免用 Rc 跨线程(应使用 Arc)。
--- ### 项目案例:高性能 WebSocket 推送网关
📁 真实项目模板,适用于简历描述与面试深挖。
项目名称高并发 WebSocket 推送网关(服务端)
业务背景支撑百万级实时消息推送,如行情、通知、协作编辑,要求低延迟(<10ms)且连接稳定。
规模指标单机 10 万+ 并发连接,峰值 5k msg/s 广播,P99 延迟 ≤ 15ms,内存占用 < 2GB。
技术栈Rust + tokio (async runtime) + tungstenite + 自定义线程池 + dashmap + mpsc per connection
核心难点① 大量长连接时避免 foreach 锁争用(使用 channel per connection + select!);② 广播消息时减少拷贝(使用 Arc<[u8]> 共享);③ 避免任务饿死(调整 tokio worker 线程数)。
面试高频追问
  • 为什么不用 thread-per-connection?→ 线程上下文切换开销太大,async 可承载 10k+ 连接/线程。
  • 如何实现广播不阻塞?→ 为每个连接创建 unbounded mpsc,广播时循环 send,若失败则关闭连接。
  • dashmap 与 RwLock<HashMap> 对比?→ dashmap 分片锁,写多时性能更好。
可迁移回答话术“该项目的核心挑战是用少量线程管理大规模 IO 事件。我选择了 tokio 多线程调度器 + per-connection 通道模型,将连接管理与业务逻辑解耦。通过避免跨 .await 持有锁和使用 Arc 共享只读数据,最终实现了单机 10 万连接下的稳定低延迟推送。这种模式也适用于消息队列消费者或实时分析管道。”
Situation 10万+ WebSocket连接 Task 低延迟广播 + 不阻塞 Action tokio + per-conn channel Result 10k连接,P99 12ms 关键设计:避免在 .await 间持有锁 使用 Arc<[u8]> 共享消息 Buffer,减少拷贝

▶ 面试中可将此项目按 STAR 逻辑叙述,强调 async 运行时选型 和 锁避免策略,体现工程落地能力。


📖 本章要点速查:线程 vs async(CPU vs IO)→ 消息传递 vs Mutex(解耦 vs 性能)→ Send/Sync 编译器保证 → 项目案例:WebSocket 网关的 async + per-conn channel 模式。