名义上在互斥条件下发生的写入之间是否存在数据竞争?

问题描述 投票:0回答:1

考虑以下代码片段:

// Main thread
int non_atom = 0;
bool x = _ // some value not known statically, depends on run-time arguments and complex calculations.

// Thread 1 and 2 are started

// Thread 1
if (!x) {
    non_atom = 5; // only write to non_atom if not x
}

// Thread 2
int bar = 0;
if (x) {
    bar = non_atom; // only access non_atom if x
}

在运行时,只有 T1 或 T2 会访问非原子,因此不应该存在数据竞争。 但假设编译器决定像这样反转 T2 中的 if 条件:

int bar = non-atom;
if (!x) {
    bar = 0;
}

这不仅仅是重新排序,而是条件及其效果的倒置。现在存在数据竞争,因为如果

x==true
两个线程都在不同步的情况下访问
non-atom

编译器允许这样做吗?假设编译器无法静态证明只有一个线程访问 x,例如因为 T1 和 T2 中的代码位于不同翻译单元的函数中。编译器是否允许动态地引入数据争用,以前没有数据争用,或者更确切地说,编译器是否足够小心,即使在任何地方都不涉及原子,也不会进行此类转换?如果我在现实世界中遇到这种情况,为了安全起见我应该使用原子吗?

这个问题与这个旧问题类似:数据竞争由 if (false) 保护...标准怎么说?

但是,在这个老问题中,条件静态地始终为假,因此很明显,永远不应该对其进行评估。在这个问题中,编译器在编译时并不知道运行时值,并且优化不是简单的重新排序。

这个问题的灵感来自于 Russ Cox 的一篇关于 Go 内存模型的帖子(ctrl-f“注意所有这些优化”),他在其中声称与 Go 相比,C++ 编译器允许此类转换。

c++ compilation language-lawyer compiler-optimization
1个回答
0
投票

编译器不允许在源代码级别上以这种方式转换代码,因为它可能不认为程序是等效的。它们并不等同,因为一个具有未定义的行为,而另一个则没有。

但是,编译器当然可以利用有关变量和体系结构的知识,以机器指令中的非直接方式实现函数。

例如,编译器知道

non_atom
具有静态存储持续时间,因此无论
non_atom
的值如何,仍然可以访问为
x
保留的内存,而不会导致错误。

那么,在我所知道的任何架构上,执行额外的

non_atom
加载的唯一问题是,它可能会导致读取撕裂值。它也可能看不到其他线程写入的值。没有其他事情可能发生。但如果原始代码没有 UB,这些都不是问题。因此,独立于
x
的值进行加载不是问题。

因此编译器可以无条件地在机器代码中进行加载。它不会影响程序的可观察行为。他们是否真的这样做是一个不同的问题,因为编译器在编译各个线程函数时不知道其他代码正在访问

non_atom

© www.soinside.com 2019 - 2024. All rights reserved.