跳到主要内容

并发 Bug 排查

问题

Rust 项目中如何排查和预防并发 Bug?

答案

Rust 能防止的 vs 不能防止的

类别Rust 编译器能防止Rust 无法防止
数据竞争✅ 借用规则 + Send/Sync
逻辑竞态❌ TOCTOU、非原子复合操作
死锁❌ 锁顺序问题
活锁❌ 互相让步

逻辑竞态条件

use std::sync::Mutex;

// ❌ TOCTOU(Time-of-Check-to-Time-of-Use)
fn bad_increment(counter: &Mutex<HashMap<String, i32>>, key: &str) {
let map = counter.lock().unwrap();
let count = map.get(key).copied().unwrap_or(0);
drop(map); // 释放锁
// 其他线程可能在此时修改了 map!
let mut map = counter.lock().unwrap();
map.insert(key.to_string(), count + 1); // 可能覆盖其他线程的写入
}

// ✅ 保持锁的持有期间完成读写
fn good_increment(counter: &Mutex<HashMap<String, i32>>, key: &str) {
let mut map = counter.lock().unwrap();
let count = map.entry(key.to_string()).or_insert(0);
*count += 1;
// 锁在作用域结束时释放
}

原子操作顺序问题

use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};

struct Metrics {
ready: AtomicBool,
value: AtomicU64,
}

// ❌ Relaxed 无法保证跨变量的顺序
fn bad_publish(m: &Metrics) {
m.value.store(42, Ordering::Relaxed);
m.ready.store(true, Ordering::Relaxed);
}

fn bad_read(m: &Metrics) -> Option<u64> {
if m.ready.load(Ordering::Relaxed) {
Some(m.value.load(Ordering::Relaxed)) // 可能读到旧值!
} else { None }
}

// ✅ 使用 Release/Acquire 保证顺序
fn good_publish(m: &Metrics) {
m.value.store(42, Ordering::Relaxed);
m.ready.store(true, Ordering::Release); // Release 保证之前的写入可见
}

fn good_read(m: &Metrics) -> Option<u64> {
if m.ready.load(Ordering::Acquire) { // Acquire 与 Release 配对
Some(m.value.load(Ordering::Relaxed))
} else { None }
}

异步并发陷阱

use tokio::sync::Mutex as AsyncMutex;
use std::sync::Mutex as StdMutex;

// ❌ 标准 Mutex 跨 await —— 可能导致死锁
async fn bad(data: &StdMutex<Vec<i32>>) {
let mut guard = data.lock().unwrap();
some_async_fn().await; // 持有标准锁跨 await!
guard.push(1);
}

// ✅ 使用 tokio::sync::Mutex
async fn good(data: &AsyncMutex<Vec<i32>>) {
let mut guard = data.lock().await;
some_async_fn().await;
guard.push(1);
}

// ✅ 或者缩小锁作用域
async fn also_good(data: &StdMutex<Vec<i32>>) {
{
let mut guard = data.lock().unwrap();
guard.push(1);
} // 锁在 await 之前释放
some_async_fn().await;
}

排查工具

工具用途
--cfg loom / loom crate并发正确性模型检查
ThreadSanitizer (TSan)运行时数据竞争检测
tokio-console异步任务监控
tracing结构化日志追踪并发流程

常见面试问题

Q1: Rust 消除了数据竞争,还需要担心并发 Bug 吗?

答案

需要。Rust 消除的是 data race(对同一内存的无同步并发读写),但无法防止 race condition(逻辑层面的竞态)。例如:

  • TOCTOU 问题(check-then-act 之间状态改变)
  • 死锁(多锁乱序)
  • 逻辑不一致(多个原子操作之间无整体原子性)

Q2: 什么情况下会出现 Rust 的并发 Bug?

答案

  1. unsafe 代码绕过借用检查
  2. Mutex 锁粒度不当导致 TOCTOU
  3. 原子操作 Ordering 选择错误导致可见性问题
  4. 死锁(多锁交叉持有)
  5. Channel 使用不当(未消费导致内存增长、意外 drop sender)

相关链接