使用std :: atomic <int>索引在大型数组上异步并行化操作的安全性如何

时间:2018-11-27 20:18:04

标签: c++ multithreading parallel-processing

我有一个并行化的例程,可以对大型指针数组中的每个对象执行一系列计算,其中计算要求每个线程都能够读取所有其他对象,但只能写入一个对象。我将其设置如下所示

#include <atomic>
#include <thread>

void threadFunction(Object** objects, int n);

std::atomic<int> idx;
int nobjects = 10000;

int main() {
  int nthreads = 4;
  Object** objects = new Object*[nobjects];

  idx = 0;
  std::thread threads[nthreads];
  for (int ii = 0; ii < nthreads; ii ++) {
    threads[ii] = std::thread(threadFunction, objects, ii);
  }

  while (idx < nobjects - 1) {}    // Wait until all the calculations have been done

  for (int ii = 0; ii < nthreads; ii ++) {
    threads[ii].join();
  }
}

void threadFunction(Object** objects, int n) {
  Object* current = NULL;
  while (idx < nobjects - 1) {
    current = objects[idx++];
    // do calculation
  }
}

其中Object是一个自定义类,但可以出于这些目的用其替代原语。我的问题是这种方式的“安全性”如何?我知道atomic类型受到保护,可以部分写入,但是我可以确定这种方法每次都适用于大型数组吗?

这可能太具体了,但我将不胜感激。谢谢

1 个答案:

答案 0 :(得分:3)

正如其他人在评论中指出的那样,您在检查循环条件和使用idx的值之间有一个竞争条件。这可能会导致您读取数组末尾的内容。您的线程功能只需要稍作调整:

void threadFunction(Object** objects, int n) {
  Object* current = NULL;
  while (true) {
    int next = idx++;
    if (next < nobjects - 1) {
      current = objects[next];
      // do calculation
    } else {
      break;
    }
  }
}

通常,很难证明无锁算法是正确的,并且只能通过仔细检查代码来完成。在任何情况下,数组的大小都与该算法的正确性无关。

使用标准库

尽管没有特别要求,但是可能值得指出的是,所有这些都可以通过标准库完成(它避免了棘手的安全性问题并处理了分区之类的问题)。类似于以下内容:

void DoCalculations(Object& obj)
{
  // details...
}

// later...

std::vector<std::unique_ptr<Object>> objects = CreateObjects();
std::for_each(
  std::execution::par,
  objects.begin(),
  objects.end(),
  [] (std::unique_ptr<Object> const& p) { DoCalculations(*p); });