如何在不使用C ++锁定的情况下防止竞争条件?

时间:2010-04-26 06:17:10

标签: c++ race-condition icc

如何防止在C ++中没有锁定或使用互斥锁/信号量的竞争条件?我正在处理嵌套的for循环,我将在数组中设置一个值:

for (int i = 0; i < m; ++i)
  for (int j = 0; j < n; ++j)
    for (int k = 0; k < o; ++k)
      array[k] += foo(...);

或多或少,我想处理这个问题,以便我可以确保同时运行的不同线程不会同时写入数组[k]。关于如何处理这个的任何建议?

编辑:我在Linux机器上运行,我还必须使用英特尔编译器。我将使用“icc”而不是“gcc”来编译代码。

7 个答案:

答案 0 :(得分:4)

对于此特定循环,将其内部翻转。将k放在外面,然后您可以将k=0放到与k=1不同的线程中,依此类推。

只要foo()不依赖于数组[k],其中k!=它的当前k,那么你就是黄金。

答案 1 :(得分:3)

假设窗口array包含LONG类型的元素,您可以执行以下操作:

for (int i = 0; i < m; ++i) 
   for (int j = 0; j < n; ++j) 
      for (int k = 0; k < o; ++k)  {
          LONG val = foo(...);
          InterlockedAdd( &array[k], val);
      }

如果您不在Windows中工作,您的平台可能会有一组类似的API。只要您的平台拥有InterlockedCompareExchange()类型的API,您就可以编写自己的InterlockedAdd()版本。

以下内容(免责声明 - 未经测试):

 int InterlockedAdd( int volatile* pDest, int operand)
 {
      int curval = *pDest;
      int oldval;

      do {
           oldval = curval;
           curval = InterlockedCompareExchange( pDest, oldval + operand, oldval);
      } while (curval != oldval);

      return oldval+operand;
 }

据我所知,Boost对原子/互锁操作的支持有限,显然只能支持对引用计数的原子操作。我不认为Boost中对互锁操作的支持不仅仅是实现细节(我目前正在处理Boost的旧版本,因此可能不再是这种情况了。)

有一些可移植库支持原子比较和交换以及其他原子操作作为界面的文档部分:

另请注意,C ++ 0x将支持比较/交换等原子操作 - 我不确定当前C ++编译器的支持级别(它似乎不是VS 2010)。

答案 2 :(得分:2)

假设数组包含整数,请使用gcc的atomic builtins__sync_fetch_and_add应该做到这一点。

答案 3 :(得分:2)

你想要的方式,它无法完成! (见sbi的评论)

您可以使用共享内存,但仍会有锁 您也可以只使用一个线程来写入和读取数组。如果您认为为它设置正确的协议更容易,请继续。

无论如何,使用锁(直接或间接)已经有了很好的解决方案。只需选择一个:)

答案 4 :(得分:0)

对线程中最内层的循环进行分区。线程T1处理范围[0,L)中的索引,线程T2处理范围[L,2L]中的索引等.L = o / n其中n是线程数。这假设foo()调用不使用可能同时计算的其他位置。

编辑:正如其他人所建议的那样,使用互锁操作会得到正确的结果,但可能会严重降低性能。 (如果内部循环很短,许多线程将竞争几个内存位置,这将有效地序列化您的程序。)

答案 5 :(得分:0)

最简单的方法(虽然不是最有效!)是将循环包装在“关键”部分。

请参阅此处Wikipedia这将在任何给定时间将循环代码限制为单个线程。

答案 6 :(得分:0)

  

使用互锁操作,如   其他人建议,会给   正确的结果,但它可能会降级   表现很糟糕。 (如果内循环   很短,很多线程都会   竞争少数内存位置,   这将有效地序列化你的   program。)link | flag

不对,我的朋友。 实际上,互锁操作优于所有类型的锁定。更确切地说:所有同步对象(关键部分,互斥体,事件等)肯定是在互锁操作方面实现的。 事实上,互锁操作是唯一可以保证同步一致性的处理器指令。实现同步对象只是不可能而根本不使用互锁操作。

另一个问题是锁定的范围。您可能想说内部循环内的同步范围(使用同步对象或直接使用互锁操作实现)可能会导致性能下降,因为它已执行多次。

嗯,这是真的。但另一方面,您只能锁定所需的持续时间。因此,您可以防止潜在的同步冲突。