潜在的数据竞赛是数据竞赛吗?



我试图了解数据竞赛和无日期竞赛之间的界限在哪里,以及未定义行为的后果是什么。

请考虑以下示例:

#include <chrono>
#include <thread>
#include <cstdlib>
#include <iostream>
#include <ctime>
#include <functional>
void write(int delay, int& value, int target) {
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
value = target;
}
int main() {
int x;
std::srand(std::time(nullptr));
auto t1 = std::thread(write, rand()%100, std::ref(x), 42);
auto t2 = std::thread(write, rand()%100, std::ref(x), 24);
t1.join();
t2.join();
std::cout << x;
}

此代码是否总是存在数据争用,还是只是偶尔?上述代码的行为是始终未根据标准定义,还是仅在有时(取决于rand()的结果(?

PS:当然,我无法知道输出是42还是24,但是在存在未定义的行为的情况下,我什至无法确定地期望两者中的任何一个,它可能是123"your cat ate my fish"

PPS:我不在乎高质量的随机性,因此rand()这个例子很好。

此代码始终具有数据争用。在两次写入之间没有发生排序之前,因此存在数据竞争。

操作系统原则上可以调度两个线程,以便两个睡眠同时返回,只要每个睡眠至少与指定的延迟时间一样长。

在可以在单个总线周期内将值存储到int的体系结构上,您要么获得42,要么24作为输出,永远不会123或任何其他值。但是,从理论上讲,您可能有一个多核处理器,其中int大于本机数据宽度,需要多个存储,在这种情况下,两个线程的存储可能会交错。这实际上发生在尝试存储到uint64_t的 32 位处理器上

许多平台可以基本上零成本提供比标准要求更强的有用行为保证,但不幸的是,标准没有提供程序可以确定哪些保证可用的方法,优化器可能会重新排列代码,而不考虑这是否会破坏利用平台本来以基本零成本提供的保证的程序。

例如,给定int x, y, *p;,请考虑以下代码片段:

x = 0x0123;
y = *p;
... maybe some computations here that use up CPU registers
x = 0x0124;

实现可能会注意到,在0x01230x0124存储之间,x的值是读取的,但不是写入的,因此可以将后者的写入从mov ax,0124h / mov _x,axmov word [_x],0124h(在 8088 上,任一序列都是六个字节加两个内存操作(更改为inc byte _x(四个字节加两个内存操作(。 但是,如果存在其他值(如 0x00FF(的干预写入,此类代码可能会严重故障。 下游代码可能将x的任何非零值视为同样可接受的,并且源代码从未要求写入底部字节为零的任何值,但如果外部写入存储在inc byte _x指令之前0x00FF,则x可能会保持零,这是非常出乎意料的。

在许多情况下,避免此类优化的成本将低于包含足以防止数据争用的内存屏障的成本,但不幸的是,除非用"原子int"替换x,否则没有很好的方法来指定这种语义,这可能需要额外的存储,并将所有访问更改为x以显式使用宽松的语义。

最新更新