无锁编程:重新排序和存储顺序语义 [英] Lock-free programming: reordering and memory order semantics
问题描述
我试图在无锁编程中站稳脚跟.阅读了有关内存排序语义的不同解释后,我想弄清楚可能发生什么重新排序.据我了解,指令可能会由编译器(由于在编译程序时进行了优化)和CPU(在运行时?)重新排序.
I am trying to find my feet in lock-free programming. Having read different explanations for memory ordering semantics, I would like to clear up what possible reordering may happen. As far as I understood, instructions may be reordered by the compiler (due to optimization when the program is compiled) and CPU (at runtime?).
对于宽松的语义, cpp参考提供了以下示例:
For the relaxed semantics cpp reference provides the following example:
// Thread 1:
r1 = y.load(memory_order_relaxed); // A
x.store(r1, memory_order_relaxed); // B
// Thread 2:
r2 = x.load(memory_order_relaxed); // C
y.store(42, memory_order_relaxed); // D
据说x和y最初为零时,代码被允许产生r1 == r2 == 42,这是因为尽管A在线程1中先于B排序,而C在线程2中先于D排序,但C之前没有排序. D从y的修改顺序出现在A之前,B从x的修改顺序出现在C之前.怎么会这样这是否意味着C和D已重新排序,所以执行顺序将为DABC?是否可以对A和B重新排序?
It is said that with x and y initially zero the code is allowed to produce r1 == r2 == 42 because, although A is sequenced-before B within thread 1 and C is sequenced before D within thread 2, nothing prevents D from appearing before A in the modification order of y, and B from appearing before C in the modification order of x. How could that happen? Does it imply that C and D get reordered, so the execution order would be DABC? Is it allowed to reorder A and B?
对于获得发布的语义,有以下示例代码:
For the acquire-release semantics there is the following sample code:
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
}
我想知道我们是否使用宽松的存储顺序而不是获取?我猜想data
的值可以在p2 = ptr.load(std::memory_order_relaxed)
之前读取,但是p2
呢?
I'm wondering what if we used relaxed memory order instead of acquire? I guess, the value of data
could be read before p2 = ptr.load(std::memory_order_relaxed)
, but what about p2
?
最后,为什么在这种情况下可以使用宽松的内存顺序?
Finally, why it is fine to use relaxed memory order in this case?
template<typename T>
class stack
{
std::atomic<node<T>*> head;
public:
void push(const T& data)
{
node<T>* new_node = new node<T>(data);
// put the current value of head into new_node->next
new_node->next = head.load(std::memory_order_relaxed);
// now make new_node the new head, but if the head
// is no longer what's stored in new_node->next
// (some other thread must have inserted a node just now)
// then put that new head into new_node->next and try again
while(!head.compare_exchange_weak(new_node->next, new_node,
std::memory_order_release,
std::memory_order_relaxed))
; // the body of the loop is empty
}
};
我的意思是head.load(std::memory_order_relaxed)
和head.compare_exchange_weak(new_node->next, new_node, std::memory_order_release, std::memory_order_relaxed)
.
总而言之,我的问题本质上是我什么时候必须关心潜在的重新排序,什么时候不关心?
To summarize all the above, my question is essentially when do I have to care about potential reordering and when I don't?
推荐答案
对于#1,编译器可能会在从x加载之前(没有依赖项)将存储发布到y,即使没有加载,也是如此x的延迟可能会延迟到cpu/内存级别.
For #1, compiler may issue the store to y before the load from x (there are no dependencies), and even if it doesn't, the load from x can be delayed at cpu/memory level.
对于#2,p2将为非零值,但是* p2和数据都不一定具有有意义的值.
For #2, p2 would be nonzero, but neither *p2 nor data would necessarily have a meaningful value.
对于#3,只有一个发布此线程创建的非原子存储的动作,这是一个发行版
For #3 there is only one act of publishing non-atomic stores made by this thread, and it is a release
您应该始终关心重新排序,或者更好的是,不承担任何顺序:C ++和硬件都不从上到下执行代码,它们只尊重依赖关系.
You should always care about reordering, or, better, not assume any order: neither C++ nor hardware executes code top to bottom, they only respect dependencies.
这篇关于无锁编程:重新排序和存储顺序语义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!