You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C++多线程中无锁写与加锁读的内存可见性问题

C++多线程中无锁写与加锁读的内存可见性问题

这个问题问得太戳痛点了,刚好是C++多线程里新手(甚至不少老鸟)容易踩的坑!咱们掰开揉碎了说:

首先给你拍板结论:线程B完全有可能看到set_ = false的陈旧值,哪怕它加了锁。别惊讶,咱们来拆解为什么:

核心误区:mutex的同步是有前提的

你以为线程B加了mutex_的锁,就能看到所有线程的最新写入?错!mutex的内存屏障(也就是保证可见性的关键),只对用同一把mutex做过“解锁-加锁”配对的操作生效。换句话说:

  • 只有当线程A在写完set_之后,解锁了mutex_,然后线程B再去加同一把mutex_,这时候B才能100%看到A写的最新值。
  • 但你的代码里,线程A碰都没碰mutex_,它的写操作是完全无锁的,和B的加锁操作之间没有任何同步关系。mutex的屏障管不到这种“野路子”的写操作。

为什么会看到陈旧值?

咱们从两个层面看:

  1. 编译器优化:编译器看到线程A里的set_ = true没有任何同步约束,完全可能把这个写操作优化到A线程的寄存器里,根本不刷回主存——在编译器眼里,既然没有同步,这个变量就只会被A自己读写,干嘛费力气刷主存?
  2. CPU缓存层面:现代CPU都是多核缓存,A核心的缓存里改了set_,但如果没有触发缓存同步的信号(比如mutex解锁会发这个信号),B核心的缓存里可能还存着set_的旧值,而且不会主动去主存刷新——毕竟缓存一致性协议也不是实时同步所有数据,得有触发条件。

更要命的:你的代码其实有未定义行为!

哦对了,差点忘了说:普通bool set_这种非原子变量,跨线程无同步的读写(A无锁写,B加锁读但没和A同步),在C++标准里属于未定义行为。什么意思?编译器可以完全无视你的逻辑,比如直接把B里的if (!set_)整个优化掉,因为它认为没有同步的话,set_的值不可能被其他线程修改。这可不是危言耸听,O2/O3优化下真的会出现这种情况。

怎么修复?

给你两个靠谱的方案:

  • 方案一:写操作也加锁:线程A的setter()里也加std::unique_lock lock(mutex_);,这样A的解锁和B的加锁形成同步对,mutex的内存屏障就能保证B看到最新的set_值。
  • 方案二:用原子变量替代普通bool:把set_改成std::atomic<bool> set_ = false;,写的时候用set_.store(true, std::memory_order_release);,读的时候用if (!set_.load(std::memory_order_acquire))。这样不用mutex也能建立同步关系,保证可见性,性能还可能更好。哪怕你用最宽松的std::memory_order_relaxed,也比普通bool安全,至少不会有未定义行为。

最后再敲个黑板:跨线程的变量访问,要么用同一把锁同步读写,要么用原子变量,别搞这种“半锁半无锁”的操作,坑多到你防不胜防!

火山引擎 最新活动