我正在使用Cygwin GCC并运行此代码:
#include#include #include using namespace std; unsigned u = 0; void foo() { u++; } int main() { vector threads; for(int i = 0; i < 1000; i++) { threads.push_back (thread (foo)); } for (auto& t : threads) t.join(); cout << u << endl; return 0; }
用线编译:g++ -Wall -fexceptions -g -std=c++14 -c main.cpp -o main.o
.
它打印1000,这是正确的.但是,由于线程覆盖了先前增加的值,我预计数量会减少.为什么这段代码不会受到相互访问的影响?
我的测试机器有4个核心,我对我所知道的程序没有任何限制.
当用foo
更复杂的东西替换共享的内容时,问题仍然存在,例如
if (u % 3 == 0) { u += 4; } else { u -= 1; }
Rob K.. 266
foo()
是如此之短,每个线程可能会在下一个线程产生之前完成.如果您在foo()
之前的随机时间内添加睡眠u++
,您可能会开始看到您的期望.
foo()
是如此之短,每个线程可能会在下一个线程产生之前完成.如果您在foo()
之前的随机时间内添加睡眠u++
,您可能会开始看到您的期望.
重要的是要理解竞争条件并不能保证代码运行不正确,只是它可以做任何事情,因为它是一个未定义的行为.包括按预期运行.
特别是在X86和AMD64机器上,某些情况下的竞争条件很少会引起问题,因为许多指令都是原子的,而且一致性保证非常高.在多处理器系统上,这些保证有所减少,其中许多指令需要锁定前缀才是原子的.
如果你的机器增量是一个原子操作,即使根据语言标准它是未定义的行为,这可能会正确运行.
具体来说,我希望在这种情况下代码可能被编译为原子Fetch和Add指令(X86程序集中的ADD或XADD),这在单处理器系统中确实是原子的,但是在多处理器系统上,这不保证是原子的和锁定的将被要求这样做.如果您在多处理器系统上运行,则会出现一个窗口,其中线程可能会干扰并产生不正确的结果.
具体来说,我使用https://godbolt.org/foo()
将您的代码编译为汇编并编译为:
foo(): add DWORD PTR u[rip], 1 ret
这意味着它只是执行一个添加指令,对于单个处理器来说它将是原子的(尽管如上所述对于多处理器系统不是这样).
我认为如果你在睡前或睡觉后睡觉,那就不是那么重要了u++
.更确切地说,操作u++
转换为代码 - 与产生调用的线程的开销相比foo
- 非常快速地执行,使得它不太可能被截获.但是,如果你"延长"操作u++
,那么竞争条件将更有可能:
void foo() { unsigned i = u; for (int s=0;s<10000;s++); u = i+1; }
结果: 694
顺便说一句:我也试过了
if (u % 2) { u += 2; } else { u -= 1; }
它给了我大部分时间1997
,但有时候1995
.
它确实患有竞争条件.放在usleep(1000);
之前u++;
,foo
我每次看到不同的输出(<1000).
虽然它确实存在,但为什么竞争条件没有为你显现的可能答案foo()
是,与启动一个线程所花费的时间相比,它是如此之快,每个线程在下一个线程开始之前完成.但...
即使使用原始版本,结果也会因系统而异:我在(四核)Macbook上尝试过,在十次运行中,我得到1000次三次,999次六次,998次.所以比赛有点罕见,但显然存在.
你编译'-g'
,有一种方法可以让bug消失.我重新编译了你的代码,但仍然没有改变'-g'
,并且比赛变得更加明显:我有1000次,999次,998次,997次,996次,992次.
回覆.添加睡眠的建议 - 这有帮助,但是(a)固定的睡眠时间使得线程仍然在开始时间(受定时器分辨率影响)的情况下偏斜,以及(b)当我们想要的是随机睡眠时将它们展开拉近他们.相反,我会将它们编码为等待启动信号,因此我可以在让它们开始工作之前创建它们.有了这个版本(有或没有'-g'
),我得到了结果,低至974,不高于998:
#include#include #include using namespace std; unsigned u = 0; bool start = false; void foo() { while (!start) { std::this_thread::yield(); } u++; } int main() { vector threads; for(int i = 0; i < 1000; i++) { threads.push_back (thread (foo)); } start = true; for (auto& t : threads) t.join(); cout << u << endl; return 0; }