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

Race Condition with writing same value in C++?

本文关键字:竞争 条件 C++      更新时间:2023-10-16

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

//parallel body with index i
int val = arr[i];
seen[val] = true;

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

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

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

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

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

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

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

在程序集级别,某些硬件可能会通过未对齐的多字节值赋值来做一些有趣的事情。 一些硬件可以(理论上;我不知道在实践中有任何(当声称是单线程写入的指令发生在相同字节上的两个不同内核中时,会引发陷阱。


所以这是C++的UB。 一旦你有了UB,你必须在编译器可以看到的任何地方审核程序生成的汇编代码。 如果你做LTO,这意味着在你的整个程序中,至少在调用或与你的代码进行UB交互的任何地方,距离不明确。

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

可能存在依赖于体系结构的约束,要求将看到的数组元素分隔一定数量,以防止竞争线程破坏在同一机器字(甚至缓存行(中冲突的值。

也就是说,如果seen定义为bool seen[N];seen长度为 N 个字节,并且每个元素都与其邻居直接相邻。如果一个线程更改元素 0,另一个线程更改元素 2,则这两个更改都发生在同一个 64 位机器字中。如果这两个更改由不同的内核(甚至在多 CPU 系统的不同 CPU 上(同时进行,它们将尝试将冲突作为整个 64 位机器字(在某些情况下更大(来解决。这样做的结果将是,写入的true之一将被获胜线程更新到相邻元素时恢复到其先前的状态(可能false(。

相反,如果您将 seen 定义为一个结构数组,每个结构都与缓存行一样大,那么您可能会让竞争线程在该结构中混合一个布尔值......但这是有风险的,因为并非所有 CPU 都会共享相同的缓存冲突验证策略、行大小等......不可避免地,会有一个 CPU 出现故障。