在C ++中写入相同值的Race Condition?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在C ++中写入相同值的Race Condition?相关的知识,希望对你有一定的参考价值。

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

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

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

答案

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

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

给定的编译器可以自由地提供更自由的内存模型。我没有意识到这一点。

您必须了解的一件事是C ++不是汇编程序宏语言。它不必生成您想象中的天真汇编程序。相反,C ++试图让编译器更容易生成汇编程序,这是一个非常不同的东西。

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

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

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


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

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

另一答案

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

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

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

以上是关于在C ++中写入相同值的Race Condition?的主要内容,如果未能解决你的问题,请参考以下文章

在C中将所有元素设置为相同的值

Kotlin如何在2个数组中找到相同值的数量

如何在ClickHouse中将具有相同列值的mysql行分组为一行?

如何在C语言中用printf()输出某个值的地址?

仅使用 C/C++ API 写入期间的 iOS 文件大小

C ++ - 指向具有相同地址但不同值的不同对象的另一个非静态成员的非静态void *成员