为什么代码在线程间变化共享variables显然不会受到竞争条件的影响?

我正在使用Cygwin GCC并运行此代码:

#include <iostream> #include <thread> #include <vector> using namespace std; unsigned u = 0; void foo() { u++; } int main() { vector<thread> 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编译: g++ -Wall -fexceptions -g -std=c++14 -c main.cpp -o main.o

它打印1000,这是正确的。 不过,由于线程覆盖了以前增加的值,我预计会有较less的数字。 为什么这个代码不会互相访问?

我的testing机器有4个内核,我对该程序没有任何限制。

当用更复杂的东西replace共享foo的内容时,问题仍然存在,例如

 if (u % 3 == 0) { u += 4; } else { u -= 1; } 

foo()是如此短以至于每个线程可能在下一个线程产生之前就结束了。 如果你在u++之前的foo()随机添加一个sleep,你可能会开始看到你期望的。

理解竞态条件并不能保证代码运行不正确,只是它可以做任何事情,这是非常重要的,因为这是一个未定义的行为。 包括按预期运行。

特别在X86和AMD64机器上,竞争条件在一些情况下很less引起问题,因为许多指令是primefaces的,并且一致性保证非常高。 在多处理器系统中这些保证有所减less,其中许多指令需要锁前缀是primefaces的。

如果你的机器上的增量是一个primefaces操作,那么即使按照语言标准它是未定义的行为,它也可能正常运行。

具体而言,我期望在这种情况下,代码可能被编译为一个primefaces获取和添加指令(X86程序集中的ADD或XADD),这在单处理器系统中确实是primefaces的,但是在多处理器系统中,这并不保证是primefaces的,将需要这样做。 如果您正在多处理器系统上运行,将会出现一个窗口,线程可能会干扰并产生不正确的结果。

具体来说,我编译你的代码程序集使用https://godbolt.org/和;foo()编译为:

 foo(): add DWORD PTR u[rip], 1 ret 

这意味着它只是执行一个单个处理器的加法指令(尽pipe如上所述,对于多处理器系统来说并非如此)。

我认为,如果你在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++;之前u++;foo ,每次看到不同的输出(<1000)。

  1. 尽pipe确实存在竞争条件,但为什么竞争条件并不存在,可能的答案是foo()与开始线程所花费的时间相比是如此之快,以致每个线程在下一个甚至可以开始之前完成。 但…

  2. 即使使用原始版本,结果也会因系统而异:我在一台(四核)Macbook上尝试了这种方式,在十次运行中,我获得了三次1000次,六次999次以及一次998次。 所以这场比赛有点罕见,但是很明显。

  3. 你用'-g'编译,这有一个使错误消失的方法。 我重新编译了你的代码,仍然没有修改,但没有'-g' ,比赛变得更加明显了:我得到了1000次,999次三次,998次两次,997次两次,996次一次,992次一次。

  4. 回覆。 (a)固定的睡眠时间使得线程仍然偏离开始时间(受定时器分辨率的影响),(b)当我们想要的是随机睡眠时,拉近他们在一起。 相反,我会编码他们等待一个开始信号,所以我可以创build它们,让他们开始工作。 有了这个版本(有或没有'-g' ),我得到的结果都是低至974,不高于998:

     #include <iostream> #include <thread> #include <vector> using namespace std; unsigned u = 0; bool start = false; void foo() { while (!start) { std::this_thread::yield(); } u++; } int main() { vector<thread> 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; }