What is the difference between atomic and critical in OpenMP?

What is the difference between atomic and critical in OpenMP?

I can do this

#pragma omp atomic
g_qCount++;

but isn't this same as

#pragma omp critical
g_qCount++;

?

95771 次浏览

对 g _ qCount 的影响是相同的,但所做的是不同的。

OpenMP 关键部分是完全通用的-它可以包围任意代码块。但是,您为这种一般性付出了代价,因为每次线程进入和退出关键部分时都会产生巨大的开销(除了序列化的固有成本之外)。

(此外,在 OpenMP 中,所有未命名的关键部分都被认为是相同的(如果你愿意,所有未命名的关键部分只有一个锁) ,所以如果一个线程在上面的一个[未命名]关键部分中,没有线程可以进入任何[未命名]关键部分。正如您可能猜到的那样,您可以通过使用命名的临界部分来解决这个问题)。

原子操作的开销要低得多。在可用的情况下,它利用硬件提供(比如)原子增量操作; 在这种情况下,进入/退出代码行时不需要锁定/解锁,它只执行硬件告诉你不能被干扰的原子增量。

好处是开销要低得多,而且一个线程处于原子操作中并不会阻止即将发生的任何(不同的)原子操作。缺点是原子支持的操作受到限制。

当然,无论哪种情况,都会产生序列化的成本。

在 OpenMP 中,所有未命名的关键部分是相互排斥的。

The most important difference between critical and atomic is that atomic can protect only a single assignment and you can use it with specific operators.

atomic的局限性很重要。他们应该详细的 OpenMP 规格MSDN提供了一个快速的备忘单,因为我不会感到惊讶,如果这不会改变。(VisualStudio2012从2002年3月开始有一个 OpenMP 实现。)引用 MSDN 的话:

表达式语句必须具有下列形式之一:

x哔哔 = expr

x++

++x

x--

--x

在前面的表达式中: x是标量类型的 lvalue表达式。expr是一个标量类型的表达式,它不引用由 x指定的对象。lvalue3不是一个过载的操作员,它是 +*-/&^lvalue0、 lvalue1或 lvalue2中的一个。

我建议在可以的时候使用 atomic,否则使用 名字关键部分。命名它们很重要; 这样可以避免调试的麻烦。

Atom 是一个单独的语句关键部分,即锁定一个语句执行

关键部分是代码块上的一个锁

一个好的编译器会像翻译第一个代码一样翻译第二个代码

关键部分:

  • 确保代码块的序列化。
  • 适当使用“名称”标签可以扩展到序列化块组。

  • 慢点!

原子操作:

  • 快多了!

  • 只能确保特定操作的序列化。

最快的方法既不是临界的也不是原子的。大约临界截面的加法比简单加法贵200倍,原子加法比简单加法贵25倍。

最快的选择(并不总是适用)是为每个线程提供自己的计数器,并在需要总和时执行 reduce 操作。

这已经是很好的解释了。然而,我们可以潜得更深一点。为了理解 OpenMP 中 原子弹临界区概念之间的核心区别,我们必须首先理解 锁定的概念。让我们回顾一下为什么需要使用

一个并行程序由多个线程执行。当且仅当我们在这些线程之间执行 同步时才会发生确定性结果。当然,线程之间并不总是需要 同步。我们指的是那些 同步是必要的情况。

为了使多线程程序中的线程 同步,我们将使用 锁定。当访问需要一次只被一个线程限制时,锁定就会发挥作用。锁定的概念实现可能因处理器而异。让我们从算法的角度来看看一个简单的锁是如何工作的。

1. Define a variable called lock.
2. For each thread:
2.1. Read the lock.
2.2. If lock == 0, lock = 1 and goto 3    // Try to grab the lock
Else goto 2.1    // Wait until the lock is released
3. Do something...
4. lock = 0    // Release the lock

The given algorithm can be implemented in the hardware language as follows. We'll be assuming a single processor and analyze the behavior of locks in that. For this practice, let's assume one of the following processors: MIPS, Alpha, 手臂 or 力量.

try:    LW R1, lock
BNEZ R1, try
ADDI R1, R1, #1
SW R1, lock

This program seems to be OK, but It is not. The above code suffers from the previous problem; 同步. Let's find the problem. Assume the initial value of lock to be zero. If two threads run this code, one might reach the SW R1,锁定 before the other one reads the 锁定 variable. Thus, both of them think that the 锁定 is free. To solve this issue, there is another instruction provided rather than simple LW and SW. It is called 读-修改-写 instruction. It is a complex instruction (consisting of subinstructions) which assures the 锁定装置锁定装置 procedure is done by only a 单身 thread at a time. The difference of 读-修改-写 compared to the simple Read and 写作 instructions is that it uses a different way of 装弹中 and Storing. It uses SW0(Load Linked) to load the lock variable and SW1(Store Conditional) to write to the lock variable. An additional SW2 is used to assure the procedure of lock acquisition is done by a single thread. The algorithm is given below.

1. Define a variable called lock.
2. For each thread:
2.1. Read the lock and put the address of lock variable inside the Link Register.
2.2. If (lock == 0) and (&lock == Link Register), lock = 1 and reset the Link Register then goto 3    // Try to grab the lock
Else goto 2.1    // Wait until the lock is released
3. Do something...
4. lock = 0    // Release the lock

当链接寄存器被重置时,如果另一个线程假定锁是空闲的,它将不能再次将增量值写入锁。因此,获得了对 锁定变量的访问的并发性。

至关重要原子弹的核心区别在于:

为什么使用锁(一个新的变量) ,而我们可以使用实际的变量(我们正在对它执行一个操作) ,作为一个锁变量?

使用 新的变量将导致 临界区,而使用 真的变量作为锁将导致 原子弹的概念。当我们对实际变量执行大量计算(多于一行)时,关键部分非常有用。这是因为,如果这些计算的结果没有写在实际的变量上,那么应该重复整个过程来计算结果。与在进入高度计算性区域之前等待锁被释放相比,这可能导致性能较差。因此,建议在执行单个计算(x + + ,x —— ,+ + x,—— x 等)时使用 原子弹指令,并在强化部分执行计算更复杂的区域时使用 至关重要指令。

Critical子句对代码块应用可变排除,并保证在给定时间只有一个线程将执行代码块,并且该线程完成代码块,而其他线程则 Wellcome 为执行代码块获取锁。

Atomic子句只适用于一个包含任何数学符号的语句,但差异不仅受表达式大小的限制。原子子句保护分配给左侧元素的地址位置,并且只保证分配给该变量。因此,您可以假设,如果语句右侧存在任何函数调用,则可以并行执行该函数调用。

#pragma omp atomic
a = 5 + fnk();

here fnk(); could be called by multiple threads at the same time but the assignment to the a must be mutually exclusive. 如下所示,fnk ()调用被另一个线程调用,我们分别得到了结果022和0。如果我们使用关键条款,情况就不会是这样。
enter image description here enter image description here