C++ std::atomic 究竟是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31978324/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 14:04:16  来源:igfitidea点击:

What exactly is std::atomic?

c++multithreadingc++11atomic

提问by Mateusz Grzejek

I understand that std::atomic<>is an atomic object. But atomic to what extent? To my understanding an operation can be atomic. What exactly is meant by making an object atomic? For example if there are two threads concurrently executing the following code:

我知道那std::atomic<>是一个原子对象。但原子到什么程度?据我了解,操作可以是原子的。使对象原子化到底是什么意思?例如,如果有两个线程并发执行以下代码:

a = a + 12;

Then is the entire operation (say add_twelve_to(int)) atomic? Or are changes made to the variable atomic (so operator=())?

那么整个操作(比如说add_twelve_to(int))是原子的吗?或者是否对变量 atomic (so operator=())进行了更改?

回答by Mateusz Grzejek

Each instantiation and full specialization of std::atomic<>represents a type that different threads can simultaneously operate on (their instances), without raising undefined behavior:

std::atomic<> 的每个实例化和完全特化都代表一种类型,不同线程可以同时对其进行操作(它们的实例),而不会引发未定义的行为:

Objects of atomic types are the only C++ objects that are free from data races; that is, if one thread writes to an atomic object while another thread reads from it, the behavior is well-defined.

In addition, accesses to atomic objects may establish inter-thread synchronization and order non-atomic memory accesses as specified by std::memory_order.

原子类型的对象是唯一没有数据竞争的 C++ 对象;也就是说,如果一个线程写入一个原子对象而另一个线程从它读取,则行为是明确定义的。

此外,对原子对象的访问可以建立线程间同步并按照 指定的顺序对非原子内存访问进行排序std::memory_order

std::atomic<>wraps operations that, in pre-C++ 11 times, had to be performed using (for example) interlocked functionswith MSVC or atomic bultinsin case of GCC.

std::atomic<>包装操作,在 C++ 之前的 11 次中,必须使用(例如)互锁函数与 MSVC 或在 GCC 的情况下使用原子 bultins来执行。

Also, std::atomic<>gives you more control by allowing various memory ordersthat specify synchronization and ordering constraints. If you want to read more about C++ 11 atomics and memory model, these links may be useful:

此外,std::atomic<>通过允许指定同步和排序约束的各种内存顺序,为您提供更多控制。如果您想阅读有关 C++ 11 原子和内存模型的更多信息,这些链接可能有用:

Note that, for typical use cases, you would probably use overloaded arithmetic operatorsor another set of them:

请注意,对于典型用例,您可能会使用重载算术运算符另一组运算符

std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this

Because operator syntax does not allow you to specify the memory order, these operations will be performed with std::memory_order_seq_cst, as this is the default order for all atomic operations in C++ 11. It guarantees sequential consistency (total global ordering) between all atomic operations.

由于运算符语法不允许您指定内存顺序,因此这些操作将使用 执行std::memory_order_seq_cst,因为这是 C++ 11 中所有原子操作的默认顺序。它保证所有原子操作之间的顺序一致性(总全局排序)。

In some cases, however, this may not be required (and nothing comes for free), so you may want to use more explicit form:

然而,在某些情况下,这可能不是必需的(并且没有什么是免费的),因此您可能需要使用更明确的形式:

std::atomic<long> value {0};
value.fetch_add(1, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(5, std::memory_order_release); // Atomic, performs 'release' operation

Now, your example:

现在,你的例子:

a = a + 12;

will not evaluate to a single atomic op: it will result in a.load()(which is atomic itself), then addition between this value and 12and a.store()(also atomic) of final result. As I noted earlier, std::memory_order_seq_cstwill be used here.

不会计算为单个原子操作:它将导致a.load()(它本身是原子的),然后在此值与最终结果12a.store()(也是原子的)之间添加。正如我之前提到的,std::memory_order_seq_cst这里会用到。

However, if you write a += 12, it will be an atomic operation (as I noted before) and is roughly equivalent to a.fetch_add(12, std::memory_order_seq_cst).

但是,如果您编写a += 12,它将是一个原子操作(正如我之前提到的)并且大致相当于a.fetch_add(12, std::memory_order_seq_cst).

As for your comment:

至于你的评论:

A regular inthas atomic loads and stores. Whats the point of wrapping it with atomic<>?

常规int具有原子加载和存储。用它包裹有atomic<>什么意义?

Your statement is only true for architectures that provide such guarantee of atomicity for stores and/or loads. There are architectures that do not do this. Also, it is usually required that operations must be performed on word-/dword-aligned address to be atomic std::atomic<>is something that is guaranteed to be atomic on everyplatform, without additional requirements. Moreover, it allows you to write code like this:

您的陈述仅适用于为存储和/或加载提供这种原子性保证的架构。有些架构不这样做。此外,通常要求必须在字/双字对齐的地址上执行操作才能是原子std::atomic<>的,这是保证在每个平台上都是原子的,没有额外的要求。此外,它允许您编写如下代码:

void* sharedData = nullptr;
std::atomic<int> ready_flag = 0;

// Thread 1
void produce()
{
    sharedData = generateData();
    ready_flag.store(1, std::memory_order_release);
}

// Thread 2
void consume()
{
    while (ready_flag.load(std::memory_order_acquire) == 0)
    {
        std::this_thread::yield();
    }

    assert(sharedData != nullptr); // will never trigger
    processData(sharedData);
}

Note that assertion condition will always be true (and thus, will never trigger), so you can always be sure that data is ready after whileloop exits. That is because:

请注意,断言条件将始终为真(因此永远不会触发),因此您始终可以确保在while循环退出后数据已准备就绪。那是因为:

  • store()to the flag is performed after sharedDatais set (we assume that generateData()always returns something useful, in particular, never returns NULL) and uses std::memory_order_releaseorder:
  • store()sharedData设置后执行到标志(我们假设generateData()总是返回有用的东西,特别是从不返回NULL)并使用std::memory_order_release顺序:

memory_order_release

A store operation with this memory order performs the releaseoperation: no reads or writes in the current thread can be reordered afterthis store. All writes in the current thread are visible in other threads that acquire the same atomic variable

memory_order_release

具有此内存顺序的存储操作执行释放操作:此存储之后,无法重新排序当前线程中的读取或写入 。当前线程中的所有写入在获取相同原子变量的其他线程中都是可见的

  • sharedDatais used after whileloop exits, and thus after load()from flag will return a non-zero value. load()uses std::memory_order_acquireorder:
  • sharedDatawhile循环退出后使用,因此load()from 标志之后将返回一个非零值。load()使用std::memory_order_acquire顺序:

std::memory_order_acquire

A load operation with this memory order performs the acquireoperation on the affected memory location: no reads or writes in the current thread can be reordered beforethis load. All writes in other threads that release the same atomic variable are visible in the current thread.

std::memory_order_acquire

具有此内存顺序的加载操作对受影响的内存位置执行获取操作:此加载之前,当前线程中的读取或写入操作不能重新排序。释放相同原子变量的其他线程中的所有写入在当前线程中都是可见的

This gives you precise control over the synchronization and allows you to explicitly specify how your code may/may not/will/will not behave. This would not be possible if only guarantee was the atomicity itself. Especially when it comes to very interesting sync models like the release-consume ordering.

这使您可以精确控制同步,并允许您明确指定您的代码可能/可能不会/将/不会/不会的行为。如果仅保证原子性本身,这将是不可能的。尤其是当涉及到非常有趣的同步模型时,例如release-consume ordering

回答by Tony Delroy

I understand that std::atomic<>makes an object atomic.

我知道这std::atomic<>使对象原子化。

That's a matter of perspective... you can't apply it to arbitrary objects and have their operations become atomic, but the provided specialisations for (most) integral types and pointers can be used.

这是一个角度问题......你不能将它应用于任意对象并使它们的操作成为原子,但是可以使用为(大多数)整型和指针提供的特化。

a = a + 12;

a = a + 12;

std::atomic<>does not (use template expressions to) simplify this to a single atomic operation, instead the operator T() const volatile noexceptmember does an atomic load()of a, then twelve is added, and operator=(T t) noexceptdoes a store(t).

std::atomic<>不(使用模板表达式)简化这对单个原子操作,而不是operator T() const volatile noexcept构件确实的原子load()a,那么12被添加,并operator=(T t) noexcept做了store(t)