0

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

考虑这个例子:

#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:当然我不知道输出是否是42or 24,但是在存在未定义行为的情况下,我什至不会肯定地期望两者中的任何一个,它可能是123or "your cat ate my fish"

PPS:我不关心高质量的随机性,因此rand()对于这个例子来说很好。

4

3 回答 3

8

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

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

于 2020-01-17T09:13:31.040 回答
2

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

于 2020-01-17T09:20:11.710 回答
1

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

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

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

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

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

于 2020-01-17T16:07:59.527 回答