6

多个线程可以安全地同时将相同的值写入同一个变量吗?

举一个具体的例子——C++ 标准是否保证以下代码在每个符合标准的系统上都可以编译、运行而没有未定义的行为并打印“true”?

#include <cstdio>
#include <thread>

int main()
{
    bool x = false;
    std::thread one{[&]{ x = true; }};
    std::thread two{[&]{ x = true; }};
    one.join();
    two.join();
    std::printf(x ? "true" : "false");
}

这是一个理论问题;我想知道它是否总是有效,而不是在实践中是否有效(或者像这样编写代码是否是一个好主意:))。如果有人能指出标准的相关部分,我将不胜感激。以我的经验,它在实践中总是有效的,但不知道它是否能保证我总是使用它std::atomic——我想知道这对于这种特定情况是否是绝对必要的。

4

2 回答 2

14

不。

您需要通过使用互斥锁或使它们成为原子来同步对这些变量的访问。

写入相同值时没有例外。您不知道编写该值涉及哪些步骤(这是潜在的实际问题),标准也不知道代码具有未定义行为的原因......这意味着您的编译器可以对您的程序造成绝对的混乱(这就是您需要避免的真正问题)。

有人会过来告诉你,这样那样的架构保证对这些大小的变量进行原子写入。但这不会改变 UB 方面。

您正在寻找的段落是:

[intro.races/2]:如果其中一个修改了内存位置 ([intro.memory]) 而另一个读取或修改了相同的内存位置,则两个表达式计算冲突。

[intro.races/21]: […] 如果程序的执行包含两个潜在的并发冲突操作,则程序的执行包含数据竞争,[…]。任何此类数据竞争都会导致未定义的行为。

......以及周围的措辞。该部分实际上非常深奥,但您实际上不需要解析它,因为这是一个经典的教科书数据竞赛,您可以在任何有关编程的书籍中阅读它。

于 2020-01-09T01:12:54.357 回答
3

从标准的角度来看,亮度是正确且准确的。

但我会给你另一个观点,为什么从硬件架构的角度来看这不是一个好主意。

如果没有内存屏障(原子、互斥体等),您可能会遇到所谓的缓存一致性问题。在多核或多处理器机器上,您的两个线程都可以设置x为,但即使您的编译器没有隐藏到寄存器中true,您的主线程也可能会打印。那是因为主线程使用的硬件缓存还没有被更新为从它所在的任何缓存行中失效。C++ 提供的原子类型和锁守卫(以及无数的操作系统原语)就是为了解决这个问题而实现的。falsexx

在任何情况下,谷歌的Cache Coherence ProblemCache Coherence Multicore。对于如何实现原子事务的特定架构实现,请查找英特尔 LOCK 前缀

于 2020-01-09T01:46:49.173 回答