OpenMP中的atomic和critical之间有什么区别?
我可以做这个
#pragma omp atomic
g_qCount++;
但是和这不一样
#pragma omp critical
g_qCount++;
?
OpenMP中的atomic和critical之间有什么区别?
我可以做这个
#pragma omp atomic
g_qCount++;
但是和这不一样
#pragma omp critical
g_qCount++;
?
Answers:
对g_qCount的作用是相同的,但是所做的不同。
OpenMP关键部分是完全通用的-它可以包围任何代码块。但是,您为这种一般性付出了代价,因为每次线程进入和退出关键部分都会产生大量开销(除了序列化的固有成本之外)。
(此外,在OpenMP中,所有未命名的关键部分都被认为是相同的(如果您愿意,所有未命名的关键部分只有一个锁),因此,如果一个线程位于上述一个[unnamed]关键部分中,则没有线程可以输入任何内容。 [未命名]关键部分。您可能会猜到,可以使用命名的关键部分来解决此问题。
原子操作的开销要低得多。如果可用,它将利用提供(例如)原子增量操作的硬件;在那种情况下,在输入/退出代码行时不需要锁定/解锁,它只是执行原子级的增量,而硬件告诉您不会受到干扰。
好处是开销要低得多,并且处于原子操作中的一个线程不会阻止任何即将发生的(不同的)原子操作。缺点是原子支持的操作受限。
当然,无论哪种情况,都会产生序列化的成本。
关键部分:
通过适当使用“名称”标签,可以扩展为序列化块组。
慢点!
原子操作:
快多了!
仅确保特定操作的序列化。
的限制atomic
很重要。它们应在OpenMP规范中详细说明。MSDN提供了一个快速备忘单,如果这种情况不会改变,我不会感到惊讶。(Visual Studio 2012从2002年3月开始实施OpenMP。)要引用MSDN:
expression语句必须具有以下形式之一:
x
binop =expr
x++
++x
x--
--x
在前面的表达式中:
x
是lvalue
具有标量类型的表达式。expr
是标量类型的表达式,它不引用所指定的对象x
。binop不是重载运算符,是中的一个+
,*
,-
,/
,&
,^
,|
,<<
,或>>
。
我建议atomic
您在可以的情况下使用,否则将其命名为关键部分。命名它们很重要;您将避免通过这种方式调试麻烦。
这里已经有很好的解释。但是,我们可以进一步深入。要了解OpenMP中原子和关键部分概念之间的核心区别,我们必须首先了解锁的概念。让我们回顾一下为什么需要使用locks。
一个并行程序正在由多个线程执行。只有当我们在这些线程之间执行同步时,才会产生确定性的结果。当然,并非总是需要线程之间的同步。我们指的是需要同步的情况。
为了同步多线程程序中的线程,我们将使用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或Power。
try: LW R1, lock
BNEZ R1, try
ADDI R1, R1, #1
SW R1, lock
这个程序似乎还可以,但事实并非如此。上面的代码存在先前的问题;同步。让我们找到问题所在。假定lock的初始值为零。如果两个线程运行此代码,则一个线程可能到达SW R1,在另一个线程读取lock变量之前将其锁定。因此,他们俩都认为锁是免费的。为了解决此问题,提供了另一条指令,而不是简单的LW和SW。这称为读取-修改-写入指令。这是一条复杂的指令(由子指令组成),可确保锁获取过程仅由单个指令完成一次线程。与简单的Read和Write指令相比,Read-Modify-Write的区别在于它使用了不同的Load and Storing方式。它使用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
重置链接寄存器后,如果另一个线程认为该锁是空闲的,则它将无法再次将递增的值写入该锁。因此,获得了访问锁变量的并发性。
关键和原子之间的核心区别在于:
为什么可以在使用实际变量(对其执行操作)的同时使用锁(新变量)作为锁变量?
对锁使用新变量将导致临界区,而将实际变量用作锁将导致原子概念。当我们对实际变量执行大量计算(不止一行)时,关键部分很有用。这是因为,如果这些计算的结果未能写入实际变量,则应重复整个过程以计算结果。与在进入高度计算区域之前等待释放锁相比,这可能导致性能下降。因此,建议您在每次要执行单个计算(x ++,x-,++ x,-x等)并使用时使用原子指令。当密集部分正在执行一个计算复杂性更高的区域时的关键指令。
原子是单个语句的关键部分,即您锁定一个语句的执行
关键部分是对代码块的锁定
一个好的编译器将以与第一个相同的方式翻译您的第二个代码
++
和*=
),而如果他们没有在硬件支持,他们可能会被替换critical
部分。