CUDA中自旋锁的正确实现

3
许多来源在CUDA中提供自旋锁的实现: 它们遵循相同的模式:
  1. LOCK:等待将锁的原子值从0更改为1
  2. 执行一些关键操作
  3. UNLOCK:通过将其值设置为0来释放锁
让我们假设我们没有warp-divergence或者换句话说,我们不使用锁进行warp间同步。
如何实现步骤1?
一些答案建议使用atomicCAS,而另一些则使用atomicExch。两者都是等效的吗?
while (0 != (atomicCAS(&lock, 0, 1))) {}
while (atomicExch(&lock, 1) != 0) {}

如何正确实现第三步?

几乎所有的资料都建议使用atomicExch

atomicExch(&lock, 0);

有一位用户提出了一个替代方案(在CUDA中实现关键部分),这个方案也是有意义的,但对他来说并不起作用(所以在CUDA中可能会导致未定义行为):

lock = 0;

在CPU上一般的自旋锁似乎是有效的,可以参考这里:https://dev59.com/h1rUa4cB1Zd3GeqPghzr#7007893。为什么我们不能在CUDA中使用它呢?

在第二步中,我们必须使用内存屏障和volatile限定符进行内存访问吗?

CUDA关于原子操作的文档(https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#atomic-functions)说明它们不保证顺序约束:

原子函数不作为内存屏障,并且不对内存操作施加同步或顺序约束

这是否意味着我们必须在临界区(2)的末尾使用内存屏障,以确保临界区(2)中的更改在解锁(3)之前对其他线程可见?

CUDA是否保证其他线程将看到一个线程在步骤(1)和(3)中使用原子操作所做的更改?

这对于内存屏障来说是不正确的(https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#memory-fence-functions):

内存屏障函数只影响线程的内存操作顺序;它们不能确保这些内存操作对其他线程可见(就像__syncthreads()对块内线程可见一样(请参阅同步函数))。

因此,原子操作也可能不正确。如果是这样,所有CUDA中的自旋锁实现都依赖于未定义行为。

如何在存在warp分歧的情况下实现可靠的自旋锁?

现在,假设我们已经回答了上面所有问题,让我们消除我们没有warp分歧的假设。在这种情况下,是否可以实现自旋锁?

主要问题(死锁)在https://wlandau.github.io/gpu/lectures/cudac-atomics/cudac-atomics.pdf的第30页中表示:enter image description here

在步骤(1)中,替换while循环为if,并像Thread/warp local lock in cudaCUDA, mutex and atomicCAS()中所建议的那样将所有3个步骤封装在单个while循环中,这是唯一的选择吗?


我认为这种机制非常健壮。 - undefined
1个回答

2
什么是实现步骤1的正确方法?有些答案建议使用atomicCAS,而其他答案建议使用atomicExch。两者是否等效? 不,它们并不等效,只有atomicCas是正确的。该代码的重点在于检查给定线程对锁从未锁定到已锁定状态的更改是否成功。atomicExch版本没有这样做,因为它在执行赋值之前不检查初始状态是否未锁定。
似乎对于CPU上的一般自旋锁,可以这样做:https://dev59.com/h1rUa4cB1Zd3GeqPghzr#7007893。为什么我们不能在CUDA中使用它? 如果您阅读该答案的评论,您将看到它在CPU上也无效。
在步骤2中的内存访问中,我们必须使用内存屏障和易失性说明符吗? 这完全取决于您的最终用途以及为什么首先使用关键部分。显然,如果您希望给定线程对全局可见内存的操作对全局可见,则需要使用屏障或原子事务来完成,并确保编译器不会将值缓存在寄存器中。
CUDA是否保证其他线程将看到步骤(1)和(3)中使用原子操作进行更改的线程所做的更改? 是的,但仅适用于执行原子操作的其他操作。原子操作意味着对给定地址执行的所有内存事务的串行化,并且当线程执行操作时,它们返回地址的先前状态。
我们如何在warp存在的情况下实现可靠的自旋锁? Warp分歧是无关紧要的。原子内存操作的串行化意味着当来自同一warp的多个线程尝试获取锁时会出现warp分歧。

1
你说"atomicExch在执行赋值操作之前不检查初始状态是否为解锁状态",但实际上如果它不是解锁状态,那么它会将值1(代表锁定)赋给它,从而导致无操作,在某种意义上,它确实进行了这个检查。 - user8044236
如果你阅读那个答案的评论,你会发现它在CPU上也是无效的。你能指出具体的评论吗?我没有看到有人明确说lock = false; // release lock是错误的。 - user8044236
"扭曲分歧是无关紧要的" 我在问题中添加了解释。扭曲分歧会导致死锁。 - user8044236
你所添加的关于死锁的所有信息都是无关紧要的。而且是错误的。 - undefined
你能解释一下死锁的问题在哪里吗?我是否误解了幻灯片和其他答案(例如https://dev59.com/yGEi5IYBdhLWcg3wX7XP#21346015),还是这些幻灯片本身就有问题? - user8044236

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接