繁体   English   中英

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

[英]Reordering of operations and lock-free data structures

假设我们有一个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并且不想使用volatile

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

对于同步,特别是原子操作的作用,似乎有一个误解:它们的目的是使代码更快! 这可能看起来与直觉相反,因此在此进行了解释: 非原子操作应与可能的速度一样快,并且故意不能保证它们如何精确地访问内存。 只要编译器和执行系统产生正确的结果,编译器iand系统就可以自由地执行他们需要或想要做的任何事情。 为了获得良好的性能,假定不存在不同线程之间的交互。

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

棘手的部分是在某种程度上找到需要哪些同步并将其最小化。 完全没有任何内容将使编译器和CPU自由地对操作进行重新排序,并且将无法工作。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM