anu*_*g86 2 c++ multithreading
在下面的例子中,我最后调用了pthread_join()两个线程(在打印总和之前).即使预期总和应为0,它也会打印任何值.我知道,如果我pthread_join(id1,NULL)在创建第二个线程之前就做了,那么它可以正常工作(确实如此),但我不明白为什么在我最后为两个线程调用join时它不应该工作.
因为只有在两个线程必须完全执行完毕后才打印sum.因此,在执行第一个线程之后,它必须已将2000000添加到变量sum中,并且第二个线程必须从总和中减去2000000应该为0
long long sum=0;
void* counting_thread(void* arg)
{
int offset = *(int*) arg;
for(int i=0;i<2000000;i++)
{
sum=sum+offset;
}
pthread_exit(NULL);
}
int main(void)
{
pthread_t id1;
int offset1 = 1;
pthread_create(&id1,NULL,counting_thread,&offset1);
pthread_t id2;
int offset2 = -1;
pthread_create(&id2,NULL,counting_thread,&offset2);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
cout<<sum;
}
Run Code Online (Sandbox Code Playgroud)
问题是sum=sum+offset;不是线程安全的.
这导致一些金额不计算在内.
正如您指定的C++,std::atomic<long long> sum; 会有所帮助,但您需要使用+=运算符,而不是线程不安全sum = sum + count;
sum += offset;
Run Code Online (Sandbox Code Playgroud)
阻止更新的互斥锁也会有所帮助.
如果没有这些更改,编译器就可以生成代码
sum函数的开头,只有一个线程应用其更改.sum添加.编译器可以在线程启动时合法地读取sum的值,向其添加n次n,并存储该值.这意味着只有一个线程可以工作.
请考虑以下汇编代码.
read sum
add offset to sum
store sum
thread1 thread2
1 read sum
2 add offset to sum read sum
3 store sum add offset to sum
4 read sum store sum
5 add offset to sum read sum
6 store sum add offset to sum
Run Code Online (Sandbox Code Playgroud)
线程2的第3行将偏移量添加到旧值,这使得线程1的第3行丢失.
在多线程系统中,缓存可能在进程的线程之间不一致.
这意味着即使在sum+=offset执行之后,另一个核心/ CPU也可能看到预先更新的值.
这允许CPU运行得更快,因为它们可以忽略在它们之间共享数据.但是,当2个线程访问相同的数据时,需要考虑这一点.
std::atomic/mutex确保: -
sum = sum + count是不可分割的).您可以在没有同步的情况下得到任何结果,因为add操作不是原子的.
您的
sum=sum+offset;
Run Code Online (Sandbox Code Playgroud)
实际上是
fetch sum to register # tmp := sum
add offset # tmp := tmp + offset
store new value # sum := tmp
Run Code Online (Sandbox Code Playgroud)
现在想象两个线程同时工作
Thread1 Thread2 Sum
tmp:= 1 tmp:=1 1
tmp:= 1+1 tmp:=1-1 1
-zzz- sum := 0 0
sum := 2 -zzz- 2
Run Code Online (Sandbox Code Playgroud)
在这个计算的serai中,线程2减法的结果丢失了
如果我稍微改变时间
Thread1 Thread2 Sum
sum := 2 -zzz- 2
-zzz- sum := 0 0
Run Code Online (Sandbox Code Playgroud)
我将失去线程1的补充
现在事情变得更糟了.如果你不同步,编译器假定不会发生任何rack(因为编译器总是信任你)
因此它将跳过提取和存储部分并将代码转换为
fetch sum to register # tmp := sum
add offset N times # for (i := 1 ; i < 2000000; i++) tmp := tmp + offset
store result # sum := tmp
Run Code Online (Sandbox Code Playgroud)
甚至
fetch sum to register # tmp := sum
add offset * N # tmp := tmp + 2000000 * offset
sore tmp # sum := tmp
Run Code Online (Sandbox Code Playgroud)
现在想象两个线程在这里同时工作
之前已经介绍了基本思想,但不仅可以将编译器归咎于此,而且可以归咎于您的平台本身.缓存机制允许更快的数据访问,但如果缓存未被同步,则不同的线程可以读取同一变量的不同值