OpenMP中的原子和关键有什么区别?

时间:2011-10-17 18:35:50

标签: openmp atomic critical-section

OpenMP中的原子和关键有什么区别?

我可以这样做

#pragma omp atomic
g_qCount++;

但与

不同
#pragma omp critical
g_qCount++;

8 个答案:

答案 0 :(得分:157)

对g_qCount的影响是相同的,但所做的是不同的。

OpenMP临界区是完全通用的 - 它可以包围任意代码块。但是,每次线程进入和退出临界区时(除了序列化的固有成本之外),都会产生很大的开销,因此需要为此付出代价。

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

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

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

当然,在任何一种情况下,都会产生序列化的成本。

答案 1 :(得分:26)

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

关键和原子之间最重要的区别是原子只能保护单个赋值,你可以将它用于特定的运算符。

答案 2 :(得分:15)

关键部分:

  • 确保代码块的序列化。
  • 可以通过正确使用“name”标签扩展到序列化块组。

  • 较慢!

原子操作:

  • 快得多!

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

答案 3 :(得分:6)

最快的方式既不重要也不原子。大约,添加临界部分比简单添加贵200倍,原子添加比简单添加贵25倍。

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

答案 4 :(得分:5)

atomic的限制非常重要。它们应在OpenMP specs上详细说明。 MSDN提供快速备忘单,因为如果不改变我也不会感到惊讶。 (Visual Studio 2012从2002年3月开始实施OpenMP。)引用MSDN:

  

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

     

x binop = expr

     

x++

     

++x

     

x--

     

--x

     

在前面的表达式中:x是带有标量类型的lvalue表达式。 expr是一个标量类型的表达式,它不引用x指定的对象。 binop 不是重载运算符,而是+*-/&,{{1}之一}},^|<<

我建议您尽可能使用>>,否则命名关键部分。命名它们很重要;你会避免以这种方式调试头痛。

答案 5 :(得分:1)

这里已有很好的解释。但是,我们可以深入一点。要了解OpenMP中 atomic 关键部分概念之间的核心差异,我们必须首先理解 lock 的概念。让我们来看看为什么我们需要使用 locks

  

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

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

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

给定的算法可以用硬件语言实现,如下所示。我们将假设一个处理器并分析其中的锁的行为。对于这种做法,我们假设以下处理器之一: MIPS Alpha ARM 电源

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

这个程序似乎没问题,但事实并非如此。上面的代码遇到了上一个问题; 同步。让我们找到问题所在。假设lock的初始值为零。如果两个线程运行此代码,则可能会在另一个线程读取 lock 变量之前到达 SW R1,锁定。因此,他们都认为 lock 是免费的。 要解决此问题,提供了另一条指令,而不是简单的 LW SW 。它被称为读 - 修改 - 写指令。这是一个复杂的指令(由子指令组成),可确保锁定获取过程一次仅由单个线程完成。与简单的 Read Write 指令相比, Read-Modify-Write 的区别在于它使用了不同的 Loading <方式< / em>和存储。它使用 LL (加载链接)加载锁定变量,使用 SC (存储条件)来写入锁定变量。附加的链接寄存器用于确保锁定获取的过程由单个线程完成。算法如下。

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

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

critical atomic 之间的核心区别来自:

  

为什么在我们可以使用实际变量(我们对其执行操作)时使用锁(一个新变量)作为锁变量?

使用变量将导致关键部分,同时使用实际变量作为锁将导致原子概念。当我们在实际变量上执行大量计算(多行)时,临界区很有用。这是因为,如果这些计算的结果未能写入实际变量,则应重复整个过程来计算结果。与在进入高度计算区域之前等待锁定释放相比,这可能导致差的性能。因此,建议每当要执行单个计算(x ++,x - ,++ x, - x等)并使用 critical <时,使用 atomic 指令/ em>指令,当密集部分完成一个计算更复杂的区域时。

答案 6 :(得分:-5)

当你需要为一条指令启用互斥时,

atomic相对性能更高。类似于omp critical的情况并不正确。

答案 7 :(得分:-5)

atomic是一个单一语句Critical部分,即你锁定一个语句执行

关键部分是对代码块的锁定

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