9.3_原子操作简介

9.3 原子操作简介

在编写传统的单线程应用程序时,程序员通常不需要使用原子操作。即使需要使用原子操作也无需担心,我们接下来将详细解释原子操作是什么,以及为什么在多线程程序中需要使用它们。为了解释原子操作,我们首先来分析C或者 C++\mathrm{C + + } 的基础知识之一,即递增运算符:

$\mathbf{x} + \mathbf{\mu} + \mathbf{\mu}$

这在标准C中的一个表达式,在执行完这条语句后,x的值应该比执行这条语句之前的值大1。这条语句中包含了哪些操作?要将x的值加1,首先要知道x的当前值。只有读取了x的值之后,才可以对x进行递增。最后,我们要将递增后的值写回x。

因此,在这个操作中包含了三个步骤,如下所示:

1)读取x中的值。
2)将步骤1中读到的值增加1。
3)将递增后的结果写回到x。

有时候,这个过程也称为读取-修改-写入(Read-Modify-Write)操作,其中第2步的递增操作也可以换成其他修改x值的操作。

现在考虑一种情况:有两个线程都需要对x的值进行递增。我们将这两个线程分别称为A和B。A和B在递增x的值时都需要执行上面三个操作。假设x的初始值为7。在理想情况下,我们

希望线程A和线程B执行表9.2中的步骤。

表9.2 两个线程同时递增x的值

由于x的起始值为7,并且由两个线程进行递增,因此在递增运算完成后,x的值变为9。根据前面的操作顺序,这确实是我们得到的结果。遗憾的是,除了这个操作顺序外,还存在其他一些操作顺序可能导致错误的结果。例如,考虑表9.3中的顺序,其中线程A和线程B的操作彼此交叉进行。

表9.3 两个线程递增 xx 中的值,并且彼此的操作相互交叉

因此,如果线程的调度方式不正确,那么最终将得到错误的结果。除了上面两种执行顺序外,这6个步骤还有许多其他的排序方式,其中有些方式能产生正确的结果,而其他的方式则不能。当把程序从单线程改写为多线程时,如果多个线程需要对共享值进行读取或者写入时,那么很可能会遇到不可预测的结果。

在前面的示例中,我们需要通过某种方式一次性地执行完读取-修改-写入这三个操作,并且在执行过程中不会被其他线程中断。具体来说,除非已经完成了这三个操作,否则其他的线程都不能读取或者写入x的值。由于这些操作的执行过程不能分解为更小的部分,因此我们将满足这种条件限制的操作称为原子操作。CUDA支持多种原子操作,当有数千个线程在内存访问上发生竞争时,这些操作能够确保在内存上实现安全的操作。

现在,我们已经看到了一个只有使用原子操作才能计算出正确结果的示例。

9.3_原子操作简介 - CUDA by Example | OpenTech