必须使用互斥锁来“获取”数组中的值吗?

时间:2011-11-01 13:34:30

标签: c++ pthreads mutex race-condition

我知道如果我将多个线程中的值分配到数组中的相同位置(或递增该值等),我将需要使用互斥锁以便值在阵列的那一部分将保持连贯。

(实施例):

for(ix = 0; ix < nx; ix++)
{
  x = x_space[ix];
  for(iy = 0; iy < ny; iy++)
  {
    y = y_space[iy];

    mutex_lock[&mut];
    sum = sum + f(x,y);
    mutex_unlock[&mut];
  }
}

但是是否还需要在代码部分周围使用互斥锁,而这些代码部分的线程可能从数组中获取值?

(实施例):

for(ix = 0; ix < nx; ix++)
{
  mutex_lock[&xmut];
  x = x_space[ix];
  mutex_unlock[&xmut];

  for(iy = 0; iy < ny; iy++)
  {
    mutex_lock[&ymut];
    y = y_space[iy];
    mutex_unlock[&ymut];

    mutex_lock[&mut];
    sum = sum + f(x,y);
    mutex_unlock[&mut];
  }
}

8 个答案:

答案 0 :(得分:2)

没有。你可以这样想:许多人可以同时看一杯水,但一次只能喝一杯。

只要您只是阅读(制作副本或其他内容),就可以了。但是,如果您正在处理没有原子操作的数据类型(或某些基本数据类型没有执行原子操作,出于内存对齐等原因)并且可能其他人可以写入那个记忆,你可以看到一个处于“半改”状态的数据,其他人正在改变它。因此,您可能需要一个互斥锁,具体取决于您的情况。

答案 1 :(得分:1)

答案是,它取决于......如果你的整数在大多数架构上都正确对齐,你将获得原子读写,因此不需要锁定。

但是,如果它们未对齐,则对它们的更新可能是非原子的,您需要锁定。除非你保证写入是原子的(即一条机器指令),否则我会把它安全地锁定。

答案 2 :(得分:0)

只要没有任何写入/重新分配的数组 read ,就不需要锁定它们。

我是否也衷心建议不那么精细的锁定?这不会很好,是我的猜测

示例基于OpenMP

#pragma omp parallel for reduction (+:sum)
for(ix = 0; ix < nx; ix++)
{
    x = x_space[ix];
    for(iy = 0; iy < ny; iy++)
    {
        y = y_space[iy];
        sum += f(x,y);
    }
}

// sum is automatically 'collected' from the parallel team threads

答案 3 :(得分:0)

如果您完全确定在阅读时价值无法改变,那么您就不需要互斥锁。因此,如果只有读取操作,则不需要互斥。

答案 4 :(得分:0)

如果您的目标是仅计算总和并单独存储,则不需要互斥锁。实际上,您的算法本质上是非常“顺序”的。实际上,您可以通过计算本地总和以及最后聚合(农民工类型问题)来完全避免使用互斥锁。

答案 5 :(得分:0)

无需使用互斥锁,只要您100%确定该数组未从其他线程调整大小/删除。

答案 6 :(得分:0)

这主要取决于你对这个总和你要做什么以及数组本身代表什么(以及总和代表什么)。 在总结它们之后,某些数组元素是否被更改是可以接受的(但是你仍在完成总和)? 如果答案是,则无需锁定。

如果答案为否,则必须在和计算的所有持续时间内锁定整个数组,并且仅在总和达到其目的后才释放锁。

答案 7 :(得分:0)

如果你正在使用OpenMP 3.1,那么有一个很好的新功能,具有原子访问权限,在这种情况下你想要

#pragma omp atomic read
some_private_var = some_shared_var[some_index];

这有两个很好的事情,一个是暗示的同花,就像做

#pragma omp flush(some_shared_var[some_index])

在读取之前,但openmp不允许刷新解除引用的值。虽然你可以在没有列表的情况下进行刷新,但是所有内容都会被刷新,因此如果在某些计算的最内层循环中,这可能会很昂贵。

另一个好处当然是读取的原子性质。请注意,some_shared_var [some_index]可以是任意大小(可能是C ++中的结构或某个对象)。如果某个其他线程想要写入这个,例如可以通过复制对象内的每个原始数据,它就不能中断原子读取。

就开销而言,对我而言,这比锁定要快得多,如果some_shared_var [some_index]是原始数据类型,那么读取可能会原子地发生,但现在我们得到了刷新。

其他一些想法:

如果读取最新值并不重要,则可以在不使用原子读取的情况下抓住机会。这使得有可能从更快的缓存值(例如CPU寄存器)读取。请注意some_shared_var [some_index]是否是一个大对象,因为它可能会被另一个线程部分写入。

我认为原子读取必须来自内存中可供所有CPU访问的某个地方,因此它仍然可以驻留在片上缓存中(例如共享L3缓存),因此您不必被迫阅读DRAM。我不是百分百肯定这总是如此,但我已经通过计算内存使用率低于和高于我的CPU片上缓存的实验来确认它是我自己的计算机。