理解 C++11 中的 `memory_order_acquire` 和 `memory_order_release` [英] Understanding `memory_order_acquire` and `memory_order_release` in C++11

查看:121
本文介绍了理解 C++11 中的 `memory_order_acquire` 和 `memory_order_release`的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读文档,更具体地说

I'm reading through the documentation and more specifically

memory_order_acquire:具有此内存顺序的加载操作执行受影响内存位置上的获取操作:无读取或当前线程中的写入可以在此加载之前重新排序.全部在释放相同原子变量的其他线程中写入的是在当前线程中可见(请参阅下面的发布-获取顺序).

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. All writes in other threads that release the same atomic variable are visible in the current thread (see Release-Acquire ordering below).

memory_order_release:具有此内存顺序的存储操作执行释放操作:当前没有读或写线程可以在此存储后重新排序.当前的所有写入线程在获取相同原子的其他线程中可见变量(请参阅下面的 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. 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)

这两点:

来自memory_order_acquire

...在此加载之前无法重新排序当前线程中的读取或写入...

... no reads or writes in the current thread can be re-ordered before this load...

来自memory_order_release

...在当前线程中没有读取或写入可以在此存储后重新排序...

... no reads or writes in the current thread can be re-ordererd after this store...

它们到底是什么意思?

还有这个例子

#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.

推荐答案

获取和释放是内存障碍.如果您的程序在获取屏障之后读取数据,您可以确保读取的数据与任何其他线程关于同一原子变量的任何先前版本一致.保证原子变量在所有线程中的读取和写入具有绝对顺序(当使用 memory_order_acquirememory_order_release 时,尽管提供了较弱的操作).这些屏障实际上将该顺序传播到使用该原子变量的任何线程.您可以使用原子来指示某事已完成"或准备好",但如果消费者读取超出该原子变量的内容,则消费者不能依赖于看到"其他内存的正确版本",原子将具有有限的价值.

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. 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.

您的代码依赖于 std::string 对象 (a) 在分配 ptr 之前已在 producer() 中构造并且 (b) 该字符串的构造版本(即它占用的内存版本)是 consumer() 读取的那个.简单地说,consumer() 会在看到分配了 ptr 时立即急切地读取字符串,所以它该死的最好看到一个有效且完全构造的对象,否则会出现糟糕的情况.在该代码中,分配 ptr 的行为"是 producer() 如何告诉"consumer 字符串准备好"的方式.内存屏障的存在是为了确保这是消费者看到的.

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 (i.e. the version of the memory it occupies) being the one that consumer() reads. 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. In that code 'the act' of assigning ptr is how producer() 'tells' consumer the string is 'ready'. The memory barrier exists to make sure that's what the consumer sees.

相反,如果 ptr 被声明为普通的 std::string * 那么编译器可以决定优化 p 并分配分配的直接寻址到ptr,然后才构造对象并分配int 数据.这对于 consumer 线程来说可能是一场灾难,该线程使用该赋值作为 producer 正在准备的对象准备就绪的指示器.准确地说,如果 ptr 是一个指针,consumer 可能永远不会看到分配的值,或者在某些架构上读取部分分配的值,其中只分配了一些字节并且它指向垃圾内存位置.然而,这些方面是关于它是原子的,而不是更广泛的内存屏障.

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. 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. 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. However those aspects are about it being atomic not the wider memory barriers.

这篇关于理解 C++11 中的 `memory_order_acquire` 和 `memory_order_release`的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆