在 C++ 中使用多线程时是否可以读取写了一半、损坏的原始变量?

Is it possible to read half-written, corrupt primitive variable when using multithreading in C++?

如果在一个线程上我将写入原始类型变量,例如 int,而另一个线程正在读取它,是否有可能读取部分修改的数据,如更多复杂的数据类型?

如果是,那么我唯一的拯救方法是 atomicmutex 还是有性能开销较小的解决方案?

理论上(以及现实生活中),是的。您必须同步访问由不同线程读取和写入的变量。

一些硬件架构可能不需要同步(在这种情况下,一个有能力的编译器应该删除它),但是一些架构有非常宽松的保证(比如 DEC Alpha - 和其他) 并且非常需要同步。

为了跨实现可移植和可预测的结果,您必须同步对变量的访问。部分 reads/writes 可以 发生(通常在晚上 3 点,当每个人都在休假时,您最重要的客户)。

更不用说缺乏同步这一事实,您的程序包含数据竞争,而数据竞争根据定义是未定义的行为。一旦你的程序包含 UB(anywhere),你的编译器就不再对它可能生成的代码有任何限制(对于你程序的 all) .因此,即使硬件保证它是安全的,编译器也可能利用理论 UB 的存在来执行会破坏您的程序的优化——而且不一定是在您期望破坏发生的地方。

按照标准的措辞是:从多个线程访问同一个非 atomic 对象,其中至少一个访问是写入,是未定义的行为。因此,您不仅可以看到 "half-written" 或 "corrupt" 原始数据, 其他 事情也是可能的,例如不相关数据的损坏、无限循环、您的计算机变得有意识并正在构建一台时光机并回到过去说服您的祖先永远不要从事会导致您出生、分段错误、从您的 USB 端口排放有毒气体等的活动。

在实践中,我不知道在 现代 体系结构上编写基本类型的对齐值时会发生这种情况,其他比 "wide" 硬件本身不支持的原始类型(例如,一些 multi-socket AMD 机器无法自动写入对齐的 128 位值)。编译器通常会发出 full-width 加载和存储,所以我不知道你会被例如绊倒的现实场景。一个编译器将一个 32 位存储转换为两个 16 位存储,尽管它 完全在其 的权利范围内。

这并不意味着您应该这样做:编译器可以做出 其他 假设,这些假设可能会误导您:例如,该值永远不会改变,因此它不会需要 re-read 。更重要的是,您可能想要的是 std::atomic 与关键位置的 std::memory_order_relaxed 一致,这通常对性能影响为零或几乎为零。不过也是一把利器。