4

我有一组由 openmp 任务处理的元素。任务可能会在数组末尾添加新元素。当然,这些元素也必须进行处理,并且可以产生新的项目。目前我正在使用此代码

int p;
#pragma omp critical
{
    p=l.n++;
}

这只是在数组末尾保留一个位置。的类型l

struct list
{
    int n;
    double *e;
}

并将p用作存储新元素的位置的索引。我想知道是否有一种方法可以在不使用关键区域的情况下执行此操作。是否有一个汇编指令复制一个值然后原子地递增原始值?

代码将在 nehalem cpu 上执行,无需担心旧机器

4

3 回答 3

6
#pragma omp atomic capture
p = l.n++;

如果硬件支持,这应该在捕获值时使用原子增量。

#pragma omp atomic在这个问题中阅读更多信息: openMP,原子与关键?

这是英特尔的#pragma omp atomic.

我尝试编译一个最小的例子gcc -fopenmp -m32 -O2 -S

int i, j;
void foo (void)
{
  #pragma omp atomic capture
  i = j++;
}

我得到的是一个简单的原子“获取和添加”,这就是我们想要的:

movl $1, %eax       # eax = 1
lock xaddl %eax, j  # atomic {swap (eax,j); j = eax + j;}
movl %eax, i        # i = eax
ret
于 2012-09-11T09:48:02.883 回答
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指令是CASx86 。

在 Microsoft Visual C++中InterlockedCompareExchange()使用CMPXCHG来实现CAS.

如果XADD不可用,InterlockedAdd()则使用CAS//实现CMPXCHGInterlockedCompareExchange()

在其他一些 CPU 上,可能还有其他可能性。有些允许原子执行一些相邻的指令。

于 2012-09-11T10:14:10.317 回答
0

这实际上只是一个返回结果的原子增量,如下所示:

mov p, 1  ; p must be a register
lock xadd [l.n], p

现在你知道了。我认为没有理由实际使用它,但有一些方法可以在不求助于汇编代码的情况下做到这一点。

于 2012-09-11T10:05:47.823 回答