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

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

本文关键字:读取 损坏 变量 原始 C++ 多线程 是否      更新时间:2023-10-16

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

如果是,那么我唯一的救援是atomic还是mutex,或者是否有性能开销较低的解决方案?

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

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

要获得跨嵌入的可移植且可预测的结果,您必须同步对变量的访问。可能会发生部分读取/写入(通常在晚上 3 点在您最重要的客户那里,当每个人都在度假时)。

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

根据标准 yes 的措辞:从多个线程访问同一个非atomic对象,其中至少一个访问是写入,是未定义的行为。因此,您不仅可以看到"半写"或"损坏"的原始数据,还可能出现其他事情,例如不相关数据的损坏,无限循环,您的计算机变得有知觉并建造时间机器并穿越到过去以说服您的祖先永远不要从事会导致您出生的活动,分段故障,USB端口排放的有毒气体, 等。

在实践中,我不知道在现代架构上如何写入基元类型的对齐值时会发生这种情况,除了硬件本身不支持的"宽"基元类型(例如,某些多插槽 AMD 机器无法以原子方式写入对齐的 128 位值)。编译器通常会发出全宽加载和存储,所以我不知道一个现实的情况,例如编译器将一个 32 位存储转换为两个 16 位存储,尽管它完全在其权限范围内

这并不意味着你应该这样做:编译器可以做出其他可能绊倒你的假设:例如,值永远不会改变,因此根本不需要重新读取。更重要的是,您可能想要的是与关键位置的std::memory_order_relaxedstd::atomic配合,这些位置通常对性能的影响为零或几乎为零。不过,这是一个锋利的工具。