我有一个由openmp任务处理的元素数组。任务可能会在数组末尾添加新元素。当然,这些元素也必须处理,并可能产生新的项目。目前我正在使用此代码
int p;
#pragma omp critical
{
p=l.n++;
}
这只是在阵列末尾保留一个位置。 l
的类型是
struct list
{
int n;
double *e;
}
和p
将用作存储新元素的位置的索引。
我想知道是否有办法在不使用关键区域的情况下执行此操作。是否有一个汇编指令复制一个值,然后原子地增加原始值?
代码将在nehalem cpu上执行,无需担心旧机器
答案 0 :(得分:6)
#pragma omp atomic capture
p = l.n++;
如果硬件支持,则应该在捕获值时使用原子增量。
在此问题中详细了解#pragma omp atomic
:openMP, atomic vs critical?
这是Intel's documentation for #pragma omp atomic
。
我尝试使用gcc -fopenmp -m32 -O2 -S
编译最小示例:
int i, j;
void foo (void)
{
#pragma omp atomic capture
i = j++;
}
我得到的是一个简单的原子'fetch and add',这就是我们想要的:
movl $1, %eax # eax = 1
lock xaddl %eax, j # atomic {swap (eax,j); j = eax + j;}
movl %eax, i # i = eax
ret
答案 1 :(得分:1)
是的,x86上有一些可能的选择。
XADD r/m, r
该指令以原子方式将第二个操作数(r)添加到第一个操作数(r / m),并使用第一个操作数的原始值(r / m)加载第二个操作数(r)。
要使用它,你需要加载第二个操作数和增量(我猜测,1,这里),第一个操作数应该是增加的内存位置。
此指令必须以LOCK前缀开头(它将使其成为原子)。
Microsoft Visual C ++中的InterlockedAdd()
函数执行此操作,并且AFAIR使用XADD
(如果可用)(从i80486开始提供)。
另一种方法是使用带有CMPXCHG
指令的循环...
伪代码:
while (true)
{
int oldValue = l.n;
int newValue = oldValue + 1;
if (CAS(&l.n, newValue, oldValue) == oldValue)
break;
}
CAS()
代表Compare And Swap
(并发编程中的常用术语),是一个尝试用新值原子替换内存中值的函数。当被替换的值等于最后提供的参数oldValue
时,替换成功。否则就失败了。 CAS
返回内存中的原始值,让我们知道替换是否成功(我们将返回值与oldValue
进行比较)。失败(返回的原始值与oldValue
不同)表示在读取oldValue
和我们尝试用newValue
替换它的那一刻之间,另一个线程更改了内存中的值。在这种情况下,我们只需重试整个过程。
CMPXCHG
指令是x86 CAS
。
在Microsoft Visual C ++ InterlockedCompareExchange()
中使用CMPXCHG
来实现CAS
。
如果XADD
不可用,则使用InterlockedAdd()
/ CAS
/ CMPXCHG
实施InterlockedCompareExchange()
。
在其他一些CPU上可能还有其他可能性。有些允许原子执行一些相邻的指令。
答案 2 :(得分:0)
这实际上只是一个返回结果的原子增量,如下所示:
mov p, 1 ; p must be a register
lock xadd [l.n], p
现在你知道了。我认为没有理由实际使用它,但有一些方法可以不使用汇编代码。