更新:编译器优化了下面的while()
条件,因此两个线程都跳过条件并输入C.S.甚至带有-O0
标志。有谁知道为什么编译器这样做?顺便说一句,声明全局变量volatile
导致程序因某些奇怪的原因而挂起......
我读了CUDA programming guide但是我对CUDA如何处理全局内存内存一致性仍然有点不清楚。 (这与内存层次结构不同)基本上,我正在运行试图破解顺序一致性的测试。我使用的算法是 Peterson的内核函数内两个线程之间互斥的算法:
flag[threadIdx.x] = 1; // both these are global
turn = 1-threadIdx.x;
while(flag[1-threadIdx.x] == 1 && turn == (1- threadIdx.x));
shared_gloabl_variable_x ++;
flag[threadIdx.x] = 0;
这非常简单。每个线程通过将其标志设置为1并通过将转向设置为另一个线程来获得关键部分来请求关键部分。在评估while()
时,如果另一个线程未设置其标志,则请求线程可以安全地进入临界区。 现在,这种方法的一个微妙问题是,如果编译器对写入进行重新排序,以便在写入turn
之前执行对flag
的写入。如果这两个线程都发生这种情况将同时在CS中结束。这很容易用普通的Pthreads来证明,因为大多数处理器都没有实现顺序一致性。 但GPU呢呢?
这两个线程都将处于相同的warp中。并且他们将以锁步模式执行他们的语句。但是当它们到达turn
变量时,它们会写入同一个变量,因此warp内执行变得序列化(无论顺序是什么)。现在,此时,获胜的线程是否继续执行while条件,还是等待另一个线程完成其写入,以便两者可以同时评估while()
?这些路径将再次分散在while()
,因为只有其中一条会赢,而另一条则会等待。
运行代码后,我会让它始终破坏SC。我读的值总是1,这意味着两个线程每次都以某种方式进入C.S.这怎么可能(GPU按顺序执行指令)? (注意:我用-O0
编译了它,因此没有编译器优化,因此没有使用volatile
)。
答案 0 :(得分:3)
编辑:由于您只有两个线程且1-threadIdx.x
有效,因此您必须使用线程ID 0和1.线程0和1将始终是同一个warp的一部分目前所有的NVIDIA GPU。 Warps执行指令SIMD方式,具有用于发散条件的线程执行掩码。你的while循环是一个不同的条件。
turn
和flags
不是 volatile
时,编译器可能会重新排序说明,并且您会看到两个线程进入CS的行为turn
和flags
volatile
时,您会看到挂起。原因是其中一个线程将在写入转向时成功,因此turn
将为0或1.假设turn==0
:如果硬件选择执行线程0的发散分支的一部分,那么一切都好。但是如果它选择执行线程1的发散分支的一部分,那么它将在while循环上旋转,线程0将永远不会轮到它,因此挂起。你可以通过确保你的两个线程处于不同的warp中来避免挂起,但我认为warp必须同时驻留在SM上,以便指令可以从两者发出并且可以取得进展。 (可能在不同的SM上使用并发warp,因为这是全局内存;但是这可能需要__threadfence()而不仅仅是__threadfence_block()。)
一般来说,这是一个很好的例子,说明为什么这样的代码在GPU上是不安全的,不应该使用。我意识到这只是一个调查实验。一般情况下,CUDA GPU没有 - 如你所说,大多数处理器都没有实现顺序一致性。
原始答案
turn
和flag
必须为volatile
,否则flag
的加载不会重复,条件turn == 1-threadIdx.X
将不会重复 - 评估,但将被视为true
。__threadfence_block()
之间应该有flag
,并存储到turn
以获得正确的订购。__threadfence_block()
(也应该声明为volatile
)。在增量之后,您可能还需要__syncthreads()
或至少__threadfence_block()
,以确保其他线程可见。 我有预感,即使在进行这些修复后,您仍可能遇到麻烦。让我们知道它是怎么回事。
顺便说一句,你在这一行中有一个语法错误,所以很明显这不是你真正的代码:
while(flag[1-threadIdx.x] == 1 and turn==[1- threadIdx.x]);
答案 1 :(得分:2)
在没有额外的内存障碍(如__threadfence())的情况下,全局内存的顺序一致性仅在给定的线程中强制执行。