在 C++ 中写入相同值的竞争条件?

McA*_*gus 2 c++ parallel-processing thread-safety race-condition

当操作写入单个常量值时,您的代码中存在竞争条件是否有任何问题?例如,如果有一个并行循环seen为另一个数组中的每个值填充一个数组arr(假设没有越界索引的问题)。关键部分可能是以下代码:

//parallel body with index i
int val = arr[i];
seen[val] = true;
Run Code Online (Sandbox Code Playgroud)

由于写入的唯一值是true不需要互斥锁,并且可能对性能有害?即使线程互相踩踏,它们也会用相同的值填充地址,对吗?

Yak*_*ont 5

C++ 内存模型不会为您提供写入相同值的免费通行证。

如果两个线程在没有同步的情况下写入非原子对象,那只是一种竞争条件。竞争条件意味着您的程序执行未定义的行为。程序执行中任何地方发生的未定义行为意味着程序的行为,无论是在未定义行为点之前还是之后,都不受 C++ 标准的任何限制。

给定的编译器可以自由地提供更自由的内存模型。我不知道任何这样做。

您必须了解的一件事是 C++ 不是汇编宏语言。它不必生成您在脑海中想象的天真的汇编程序。相反,C++ 试图让您的编译器轻松生成汇编程序,这是非常不同的事情。

编译器可以并且确实确定“如果 X 发生,我们会得到未定义的行为;因此我将在生成代码时围绕 X 不会发生的事实进行优化”。在这种情况下,编译器可以证明具有定义行为的程序可以val在两个不同的非同步线程中具有相同的行为。

所有这些都可能在生成任何程序集之前很久就发生了。

在程序集级别,一些硬件可能会用未对齐的多字节值分配来做一些有趣的事情。当声称是单线程写入的指令出现在相同字节的两个不同内核中时,某些硬件可能(理论上;我在实践中没有意识到)引发陷阱。


所以这是 C++ 中的 UB。一旦你有了 UB,你就必须在接触它的编译器可以看到的任何地方审计你的程序生成的汇编代码。如果您使用 LTO,这意味着在您的整个程序中,至少在所有调用或与您执行 UB 的代码交互的地方,距离都不清楚。

只需编写定义的行为。并且只有当这被证明是一个关键任务性能瓶颈时,你才应该花更多的精力来优化它(首先是更快定义的行为,只有当它失败时你才考虑 UB)。