简体   繁体   English

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

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

Assume we have a Container maintaining a set of int values, plus a flag for each value indicating whether the value is valid. 假设我们有一个Container维护一组int值,以及每个值的标志,指示该值是否有效。 Invalid values are considered to be INT_MAX . 无效值被认为是INT_MAX Initially, all values are invalid. 最初,所有值均无效。 When a value is accessed for the first time, it is set to INT_MAX and its flag is set to valid. 首次访问值时,将其设置为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;
};

Now, another thread reads container values concurrently: 现在,另一个线程同时读取容器值:

// 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;
}

This is perfectly valid code, but it is crucial that lines (*) and (**) are executed in the given order. 这是完全有效的代码,但是至关重要的是按给定的顺序执行(*)(**)

  1. Does the standard guarantee in this case that the lines are executed in the given order? 在这种情况下,标准是否保证按给定顺序执行行? At least from a single-threaded perspective, the lines could be interchanged, couldn't they? 至少从单线程的角度来看,行可以互换,不是吗?
  2. If not, what is the most efficient way to ensure their order? 如果没有,最有效的方式来确保他们的订单? This is high-performance code, so I cannot go without -O3 and do not want to use volatile . 这是高性能的代码,所以我不能没有-O3并且不想使用volatile

There is no synchronization here. 这里没有同步。 If you access these values from one thread and change them from another thread you got undefined behavior. 如果您从一个线程访问这些值,然后从另一个线程更改它们,则会出现未定义的行为。 You'd either need a lock around all accesses in which case things are fine. 您可能需要对所有访问进行锁定,在这种情况下,情况会很好。 Otherwise you'll need to make all your std::vector elements atomic<T> and you can control visibility of the values using the appropriate visibility parameters. 否则,您将需要使所有std::vector元素成为atomic<T>并且可以使用适当的可见性参数来控制值的可见性。

There seems to be a misunderstanding of what synchronization and in particular atomic operations do: their purpose is to make code fast! 对于同步,特别是原子操作的作用,似乎有一个误解:它们的目的是使代码更快! That may appear counter intuitive so here is the explanation: non-atomic operations should be as fast as possibe and there are deliberately no guarantees how they access memory exactly. 这可能看起来与直觉相反,因此在此进行了解释: 非原子操作应与可能的速度一样快,并且故意不能保证它们如何精确地访问内存。 As long as the compiler and execution system produce the correct results the compiler iand system are free to do whatever they need or want to do. 只要编译器和执行系统产生正确的结果,编译器iand系统就可以自由地执行他们需要或想要做的任何事情。 To achieve good performance interaction between different threads are assumed to not exist. 为了获得良好的性能,假定不存在不同线程之间的交互。

In a concurrent system there are, however, interactions betwwen threads. 但是,在并发系统中,线程之间存在交互。 This is where atomic operations enter the stage: they allow the specification of exactly the necessary synchronization needed. 这是原子操作进入阶段的地方:它们允许确切说明所需的必要同步 Thus, they allow to tell the compiler the minimal constraints it has to obey to make the thread unteraction correct. 因此,它们允许告诉编译器必须遵循的最小约束,以使线程正确操作无误。 The compiler will use these indicators to generate the best possible code to achieve what is specified. 编译器将使用这些指示符来生成最佳代码,以实现指定的目标。 That code may be identical to code not using any synchronization although in practice it is normally necessary to also prevent the CPU from reordering operations. 该代码可能与不使用任何同步的代码相同,尽管在实践中通常还需要防止CPU重新排序操作。 As a result, correct use of the synchronization results in the most efficient code with only the absolutely necessary overhead. 结果,正确使用同步会导致最高效的代码,而只有绝对必要的开销。

The tricky part is to some extent finding which synchronizations are needed and to minimize these. 棘手的部分是在某种程度上找到需要哪些同步并将其最小化。 Simply not having any will allow the compiler and the CPU to reorder operations freely and won't work. 完全没有任何内容将使编译器和CPU自由地对操作进行重新排序,并且将无法工作。

Since the question mentioned volatile please note that volatile is entirely unrelated to concurrency! 由于提到的问题volatile请注意, volatile与并发完全无关! The primary purpose for volatile is to inform the system that an address points to memory whose access may have side effects. volatile的主要目的是通知系统地址指向其访问可能有副作用的内存。 Primarily it is used to have memory mapped I/O or hardware control be accessible. 首先,它用于使内存映射的I / O或硬件控制可访问。 Die to the potential of side effects it one of the two aspects of C++ defining the semantics of programs (the other one is I/O using standard library I/O facilities). 死于副作用的可能是C ++定义程序语义的两个方面之一(另一个是使用标准库I / O设施的I / O)。

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

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