重新排序操作和无锁数据结构

时间:2016-12-23 10:25:09

标签: c++ gcc c++14 compiler-optimization lock-free

假设我们有NSString *req = @"is"; NSMutable *mutableArray = [@[@"this",@"is",@"an",@"array"]mutableCopy]; NSArray *tempArray = [mutableArray copy]; if([tempArray containsObject:req]){ // req is contained in the array [mutableArray removeObject:req]; //remove the object } else{ // req is not contained in the array } // update the tempArray if needed 维护一组Container值,并且每个值都有一个标志,指示该值是否有效。无效值被视为int。最初,所有值都无效。第一次访问某个值时,它将设置为INT_MAX并且其标志设置为有效。

INT_MAX

现在,另一个线程同时读取容器值:

struct Container {
  int& operator[](int i) {
    if (!isValid[i]) {
      values[i] = INT_MAX; // (*)
      isValid[i] = true;   // (**)
    }
    return values[i];
  }
  std::vector<int> values;
  std::vector<bool> isValid;
};

这是完全有效的代码,但以给定顺序执行行// This member is allowed to overestimate value i, but it must not underestimate it. int Container::get(int i) { return isValid[i] ? values[i] : INT_MAX; } (*)至关重要。

  1. 在这种情况下,标准是否保证线路按给定顺序执行?至少从单线程的角度来看,这些线可以互换,不是吗?
  2. 如果没有,确保订单的最有效方法是什么?这是高性能代码,因此我不能不使用(**)并且不想使用-O3

1 个答案:

答案 0 :(得分:5)

这里没有同步。如果从一个线程访问这些值并从另一个线程更改它们,则会得到未定义的行为。你需要锁定所有访问,在这种情况下一切都很好。否则,您需要制作所有std::vector元素atomic<T>,并且可以使用适当的可见性参数控制值的可见性。

似乎存在对同步和特定原子操作的误解:它们的目的是使代码快速运行!这可能看起来反直觉所以这里是解释:非原子操作应该尽可能快,并且故意无法保证它们如何准确地访问内存。只要编译器和执行系统产生正确的结果,编译器和系统就可以自由地做他们需要或想做的事情。为了实现良好的性能,假设不存在不同线程之间的交互。

然而,在并发系统中,线程之间存在交互。这是原子操作进入阶段的地方:它们允许指定所需的完全必要的同步。因此,它们允许告诉编译器必须遵守的最小约束,以使线程无法正确。编译器将使用这些指示符生成最佳代码以实现指定的内容。该代码可能与不使用任何同步的代码相同,但在实践中通常还需要防止CPU重新排序操作。因此,正确使用同步会产生最有效的代码,只有绝对必要的开销。

棘手的部分是在某种程度上找到需要哪些同步并最小化这些同步。简单地说,没有任何东西将允许编译器和CPU自由地重新排序操作,并且不起作用。

由于提到volatile的问题,请注意volatile 完全与并发无关! volatile的主要目的是通知系统地址指向其访问可能有副作用的内存。它主要用于访问内存映射I / O或硬件控制。死于潜在的副作用,它是定义程序语义的C ++的两个方面之一(另一个是使用标准库I / O工具的I / O)。