我编写了一个非常简单的代码,请求线程0更新全局变量,而其他线程继续读取该变量。但是我发现其他线程并没有真正获得该值。
代码在这里,很简单。任何人都可以给我任何建议如何解决它? 非常感谢
__global__ void addKernel(int *c)
{
int i = threadIdx.x;
int j = 0;
if (i == 0)
{
while(*c < 2000){
int temp = *c;
printf("*c = %d\n",*c);
atomicCAS(c,temp, temp+1);
}
}else{
while(*c < 1000)
{
j++;
}
}
}
答案 0 :(得分:2)
我想做一个类比:想象一下,原子操作是互斥体:对于一个定义良好的程序,访问共享资源的两个线程必须两个同意使用互斥锁专门访问资源。如果其中一个线程在没有首先持有互斥锁的情况下访问资源,则结果是未定义的。
对于原子论也是如此:如果您决定将内存中的特定位置视为原子变量,那么访问该位置的所有线程都应该同意并将其视为具有意义的程序。您应该仅通过原子加载和存储来操作它,而不是非原子操作和原子操作的组合。
换句话说,这个:
atomicCAS(c,temp, temp+1);
包含原子加载比较存储。生成的指令将一直向下到全局内存 load c
,进行比较,然后一直向下到全局内存到 store 新价值。
但是这个:
while(*c < 2000)
无论如何都不是原子的。编译器(和硬件)不知道c
可能已被另一个线程修改过。因此,它不会一直向下到全局内存,而只是从最快的可用缓存中读取。可能编译器甚至会将变量放在寄存器中,因为在当前线程中没有看到其他人修改它。
你想要的是(想象的):
while (atomicLoad(c) < 2000)
但据我所知,在撰写本文时,CUDA中没有这样的结构。
在这方面,volatile
限定符可能有帮助:它告诉编译器不优化变量,并将其视为“可从外部源修改”。这将触发每次读取变量的负载,但我不确定此负载是否会绕过所有缓存。在实践中,它可能有效,但理论上我认为你不应该依赖它。此外,这还将禁用对该变量的任何优化(例如常量传播或将变量提升到寄存器以获得更好的性能)。
你可能想尝试下面的黑客攻击(我还没试过):
while(atomicAdd(c, 0) < 2000)
这将发出从全局内存加载的原子指令,因此应该看到c
的最新值。但是,它还引入了一个(在这种情况下无用)原子库。