避免数据竞争的核心是遵循互斥或无共享原则:用mutex保护共享可变状态,用atomic替代简单变量,用thread_local或不可变数据消除共享,用condition_variable/future等高级原语协作。

避免数据竞争的核心是确保多个线程对共享数据的访问满足“互斥”或“无共享”原则——要么不让它们同时读写,要么干脆不共享可变数据。
用互斥锁保护共享可变状态
最直接的方式是用 std::mutex(或 std::shared_mutex)包裹临界区。注意:锁必须覆盖所有访问该数据的路径,包括读和写;且要避免死锁、忘记解锁、锁粒度太粗等问题。
- 优先使用 std::lock_guard 或 std::unique_lock 实现 RAII 自动加锁/解锁,别手动调
lock()/unlock() - 多个互斥量一起加锁时,统一按地址顺序或使用 std::scoped_lock 防止死锁
- 不要在持有锁时调用可能阻塞或调用未知第三方代码的函数(比如 I/O、回调、虚函数)
用原子操作替代简单共享变量
对单个内置类型(如 int、bool、指针)的读写,若只需基本同步语义(如计数器、标志位),可用 std::atomic
- 声明为 std::atomic
counter{0}; ,用counter.fetch_add(1)替代++counter - 注意内存序(memory order):默认 std::memory_order_seq_cst 最安全但稍慢;高频场景可按需降级(如
relaxed用于计数,acquire/release用于同步临界资源) - 原子操作不能替代锁来保护多步逻辑(例如“先检查再修改”的复合操作),此时仍需互斥量或 std::atomic
::compare_exchange_weak
消除共享:用线程局部存储或不可变数据
不共享,就无竞争。这是最彻底的方案。
立即学习“C++免费学习笔记(深入)”;
- 用 thread_local 声明每个线程独享的变量(如缓存、随机数生成器状态),注意其初始化和析构在线程生命周期内发生
- 传递只读数据(如 const std::string&、std::shared_ptr
)而非可变引用,配合 std::make_shared 构造不可变对象 - 函数式风格编程:尽量用纯函数(无副作用、不依赖外部状态)、返回新对象而非修改原对象(如用
vector+ 修改,而非就地new_v = old_v; push_back)
用高级同步原语简化复杂协作
互斥锁和原子变量解决“访问控制”,但线程间协作(如等待条件、生产者-消费者)需要更高层工具。
-
std::condition_variable 配合 std::unique_lock 实现条件等待,务必在
wait的 lambda 中检查谓词(防止虚假唤醒) - std::promise/std::future 和 std::packaged_task 适合一次性的结果传递;std::async 可启动异步任务并获取 future
- C++17 起可用 std::shared_mutex 支持多读单写,适合读多写少场景(如配置缓存)
基本上就这些。关键不是堆砌工具,而是根据数据访问模式选对抽象:频繁读写同一块内存 → 锁;单值开关或计数 → 原子;天然隔离或只读 → 拒绝共享;需要等待或通知 → 条件变量或 future。写多线程代码时,先问自己:“这个变量,真需要被多个线程同时看到并修改吗?”








