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