[英]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_acquire从memory_order_acquire
... no reads or writes in the current thread can be re-ordered before this load...
...在此加载之前不能重新排序当前线程中的读取或写入...
from memory_order_release从memory_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.可以使用非松弛
atomic
或mutex
。 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:注意两个不同的操作:
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_acquire
和memory_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.