简体   繁体   English

理解 C++11 中的 `memory_order_acquire` 和 `memory_order_release`

[英]Understanding `memory_order_acquire` and `memory_order_release` in C++11

I'm reading through the documentation and more specifically我正在阅读文档,更具体地说

memory_order_acquire : A load operation with this memory order performs the acquire operation on the affected memory location: no reads or writes in the current thread can be reordered before this load. memory_order_acquire :具有此内存顺序的加载操作对受影响的内存位置执行获取操作:在此加载之前,当前线程中的读取或写入不能重新排序。 All writes in other threads that release the same atomic variable are visible in the current thread (see Release-Acquire ordering below).释放相同原子变量的其他线程中的所有写入在当前线程中都是可见的(请参阅下面的 Release-Acquire 排序)。

memory_order_release : A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. memory_order_release :具有此内存顺序的存储操作执行释放操作:在此存储之后无法重新排序当前线程中的读取或写入。 All writes in the current thread are visible in other threads that acquire the same atomic variable (see Release-Acquire ordering below) and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic (see Release-Consume ordering below)当前线程中的所有写入在获取相同原子变量的其他线程中都是可见的(请参阅下面的 Release-Acquire 排序),并且携带对原子变量的依赖的写入在使用相同原子变量的其他线程中变得可见(请参阅 Release-Consume下单)

These two bits:这两个位:

from memory_order_acquirememory_order_acquire

... no reads or writes in the current thread can be re-ordered before this load... ...在此加载之前不能重新排序当前线程中的读取或写入...

from memory_order_releasememory_order_release

... no reads or writes in the current thread can be re-ordererd after this store... ...在此存储之后,无法重新排序当前线程中的读取或写入...

What exactly do they mean?它们究竟是什么意思?

There's also this example还有这个例子

#include <thread>
#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;
int data;

void producer()
{
    std::string* p  = new std::string("Hello");
    data = 42;
    ptr.store(p, std::memory_order_release);
}

void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_acquire)))
        ;
    assert(*p2 == "Hello"); // never fires
    assert(data == 42); // never fires
}

int main()
{
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join(); t2.join();
}

But I cannot really figure where the two bits I've quoted apply.但我真的不知道我引用的两个位在哪里适用。 I understand what's happening but I don't really see the re-ordering bit because the code is small.我明白发生了什么,但我没有真正看到重新排序的位,因为代码很小。

The work done by a thread is not guaranteed to be visible to other threads.一个线程完成的工作不能保证对其他线程可见。

To make data visible between threads, a synchronization mechanism is needed.为了使数据在线程之间可见,需要一种同步机制。 A non-relaxed atomic or a mutex can be used for that.可以使用非松弛atomicmutex It's called the acquire-release semantics.它被称为获取-释放语义。 Writing a mutex "releases" all memory writes before it and reading the same mutex "acquires" those writes.编写互斥锁会“释放”它之前的所有内存写入,并读取相同的互斥锁“获取”这些写入。

Here we use ptr to "release" work done so far ( data = 42 ) to another thread:这里我们使用ptr将迄今为止完成的工作( data = 42 )“释放”到另一个线程:

    data = 42;
    ptr.store(p, std::memory_order_release); // changes ptr from null to not-null

And here we wait for that, and by doing that we synchronize ("acquire") the work done by the producer thread:在这里我们等待它,通过这样做我们同步(“获取”)生产者线程完成的工作:

    while (!ptr.load(std::memory_order_acquire)) // assuming initially ptr is null
        ;
    assert(data == 42);

Note two distinct actions:注意两个不同的操作:

  1. we wait between threads (the synchronization step)我们在线程之间等待(同步步骤)
  2. as a side effect of the wait , we get to transfer work from the provider to the consumer (the provider releases and the consumer acquires it)作为等待的副作用,我们可以工作从提供者转移到消费者(提供者发布,消费者获取它)

In the absence of (2), eg when using memory_order_relaxed , only the atomic value itself is synchronized.在没有 (2) 的情况下,例如当使用memory_order_relaxed ,只有atomic值本身是同步的。 All other work done before/after isn't, eg data won't necessarily contain 42 and there may not be a fully constructed string instance at the address p (as seen by the consumer).之前/之后完成的所有其他工作都不是,例如data不一定包含42并且地址p处可能没有完全构造的string实例(如消费者所见)。

For more details about acquire/release semantics and other details of the C++ memory model I would recommend watching Herb's excellent atomic<> weapons talk on channel9 , it's long but is fun to watch.有关获取/释放语义和 C++ 内存模型的其他详细信息的更多详细信息,我建议您在 channel9 上观看 Herb 出色的atomic<> 武器演讲,它很长但很有趣。 And for even more details there's a book called "C++ Concurrency in Action" .有关更多详细信息,有一本书名为“C++ Concurrency in Action”

If you used std::memory_order_relaxed for the store, the compiler could use the "as-if" rule to move data = 42;如果您将std::memory_order_relaxed用于存储,则编译器可以使用“as-if”规则来移动data = 42; to after the store, and consumer could see a non-null pointer and indeterminate data .到存储之后, consumer可以看到非空指针和不确定data

If you used std::memory_order_relaxed for the load, the compiler could use the "as-if" rule to move the assert(data == 42);如果您使用std::memory_order_relaxed进行加载,则编译器可以使用“as-if”规则来移动assert(data == 42); to before the load loop.到加载循环之前。

Both of these are allowed because the value of data is not related to the value of ptr这两个都是允许的,因为data的值与ptr的值无关

If instead ptr were non-atomic, you'd have a data race and therefore undefined behaviour.如果ptr是非原子的,则会出现数据竞争,因此会出现未定义的行为。

Acquire and Release are Memory Barriers.获取和释放是内存障碍。 If your program reads data after an acquire barrier you are assured you will be reading data consistent in order with any preceding release by any other thread in respect of the same atomic variable.如果您的程序在获取障碍之后读取数据,您可以确信您将读取与任何其他线程关于同一原子变量的任何先前版本一致的数据。 Atomic variables are guaranteed to have an absolute order (when using memory_order_acquire and memory_order_release though weaker operations are provided for) to their reads and writes across all threads.原子变量在所有线程中的读取和写入保证具有绝对顺序(当使用memory_order_acquirememory_order_release尽管提供了较弱的操作)。 These barriers in effect propagate that order to any threads using that atomic variable.这些屏障实际上将该顺序传播到使用该原子变量的任何线程。 You can use atomics to indicate something has 'finished' or is 'ready' but if the consumer reads beyond that atomic variable the consumer can't be rely on 'seeing' the right 'versions' of other memory and atomics would have limited value.您可以使用原子来指示某事已“完成”或“准备好”,但如果消费者读取超出该原子变量的内容,则消费者不能依赖于“看到”其他内存的正确“版本”,原子将具有有限的价值.

The statements about 'moving before' or 'moving after' are instructions to the optimizer that it shouldn't re-order operations to take place out of order.关于“移动之前”或“移动之后”的语句是给优化器的指令,它不应该重新排序操作以无序进行。 Optimizers are very good at re-ordering instructions and even omitting redundant reads/writes but if they re-organise the code across the memory barriers they may unwittingly violate that order.优化器非常擅长重新排序指令,甚至省略冗余读/写,但如果他们跨内存屏障重新组织代码,他们可能会在不知不觉中违反该顺序。

Your code relies on the std::string object (a) having been constructed in producer() before ptr is assigned and (b) the constructed version of that string (ie the version of the memory it occupies) being the one that consumer() reads.您的代码依赖于std::string对象(a)在分配ptr之前已在producer()构造并且(b)该字符串的构造版本(即它占用的内存版本)是consumer()读。 Put simply consumer() is going to eagerly read the string as soon as it sees ptr assigned so it damn well better see a valid and fully constructed object or bad times will ensue.简单地说, consumer()会在看到ptr被分配后立即急切地读取字符串,所以它该死的更好地看到一个有效且完全构造的对象,否则会出现糟糕的情况。 In that code 'the act' of assigning ptr is how producer() 'tells' consumer the string is 'ready'.在该代码中,分配ptr “行为”是producer() “告诉” consumer字符串“准备好”的方式。 The memory barrier exists to make sure that's what the consumer sees.内存屏障的存在是为了确保这是消费者看到的。

Conversely if ptr was declared as an ordinary std::string * then the compiler could decide to optimize p away and assign the allocated address directly to ptr and only then construct the object and assign the int data.相反,如果ptr被声明为普通的std::string *那么编译器可以决定优化p并将分配的地址直接分配给ptr ,然后才构造对象并分配int数据。 That is likely a disaster for the consumer thread which is using that assignment as the indicator that the objects producer is preparing are ready.对于使用该分配作为对象producer准备就绪的指示器的consumer线程来说,这可能是一场灾难。 To be accurate if ptr were a pointer the consumer may never see the value assigned or on some architectures read a partially assigned value where only some of the bytes have been assigned and it points to a garbage memory location.准确地说,如果ptr是一个指针, consumer可能永远不会看到分配的值,或者在某些体系结构上读取部分分配的值,其中仅分配了一些字节并且它指向垃圾内存位置。 However those aspects are about it being atomic not the wider memory barriers.然而,这些方面是关于它是原子的,而不是更广泛的内存屏障。

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

相关问题 C++11 memory_order_acquire 和 memory_order_release 语义? - C++11 memory_order_acquire and memory_order_release semantics? 与 memory_order_acquire 和 memory_order_release 的原子交换 - atomic exchange with memory_order_acquire and memory_order_release C++ 标准如何使用 memory_order_acquire 和 memory_order_release 防止自旋锁互斥锁中的死锁? - How C++ Standard prevents deadlock in spinlock mutex with memory_order_acquire and memory_order_release? 可以分别使用std :: memory_order_acquire和memory_order_release吗? - Can std::memory_order_acquire and memory_order_release be used separately? 什么时候可以从 compare_exchange 中安全删除 memory_order_acquire 或 memory_order_release? - When can memory_order_acquire or memory_order_release be safely removed from compare_exchange? C++ memory_order_acquire/release问题 - C++ memory_order_acquire/release questions MOV x86指令是否实现了C ++ 11 memory_order_release原子存储? - Does the MOV x86 instruction implement a C++11 memory_order_release atomic store? 为什么 c++ singleton 需要 memory_order_acquire - why c++ singleton need memory_order_acquire memory_order_consume 和 memory_order_acquire 的区别 - Difference between memory_order_consume and memory_order_acquire 为什么 memory_order_release 支持直到 C++20? - Why memory_order_release support until C++20?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM