如何保证在存储发生之前加载完成?
Posted
技术标签:
【中文标题】如何保证在存储发生之前加载完成?【英文标题】:How to guarantee that load completes before store occurs? 【发布时间】:2020-02-26 18:18:57 【问题描述】:在下面的代码中,如何确保 ptr 在 *ptr 被加载/分配/“提取”之后才增加?
extern int arr[some_constexpr]; // assume pre-populated
extern int* ptr; // assume points to non-atomic arr
int a = *ptr;
// want "memory barrier/fence" here
++ptr;
原子指针能否确保正确的排序/排序?
#include <atomic>
extern int arr[some_constexpr];
extern std::atomic<int*> ptr;
int a = *(ptr.load());
// implicit "memory barrier" achieved here by use of atomics?
ptr.store(ptr + 1);
这与两个线程之间共享的无锁队列有关。我想确保在更新指针之前与指针关联的数据不会丢失/损坏。
【问题讨论】:
你的第一个例子不是很好,因为 C++ 保证结果“好像”语句是按顺序执行的。如果有线索在起作用,您必须展示它们之间的确切关系。 等一下;int* ptr = arr;
在第一种情况下是局部变量吗?它的初始化器是一个编译时(或链接时)常量;静态存储中数组的地址。
您可能想查看memory_order_acquire
和memory_order_release
以获取和放下队列的指针。令人高兴的是,一个无锁的一个生产者,一个消费者队列比多个生产者和/或多个消费者更简单。
@abc: "但它需要一个理解多线程和原子的博士学位才能实现这样的强制执行?" 问题是你正在努力思考它在“指令”和“重新排序”等方面,而不是在 C++ 线程内存模型方面。您想要 是能够在一个线程中设置一个对另一个线程可见的值。基本上你已经把它描述为一个 XY 问题。
@NicolBolas 是的,我想可能是这样。作为一个嵌入式人员,我的工作接近“金属”,所以我认为硬件实际上在做什么。我想我必须退后一步,考虑一下 C++ 置于物理执行之上的抽象。
【参考方案1】:
当ptr
为std::atomic<int*>
、++ptr
或ptr++
或ptr.fetch_add(1, std::memory_order_acq_rel)
时,确保在此操作之前/之后没有重新排序之前/之后的加载/存储。
++ptr
或ptr++
本质上是ptr.fetch_add(1, std::memory_order_seq_cst)
和std::memory_order_seq_cst
几乎总是过度杀伤(不能举一个不是的例子)。
更高效的单个阅读器是:
int arr[some_constexpr];
std::atomic<int*> ptr;
int* p = ptr.load(std::memory_order_acquire);
int element = *p;
ptr.store(p + 1, memory_order_release);
以上基本就是boost::lockfree::spsc_queue
是如何实现的。
附带说明,boost::lockfree::spsc_queue
是真正的wait-free(最强的非阻塞进度保证)队列。 push
/pop
操作所做的是 1 个 relaxed
加载、1 个 acquire
加载和 1 个 release
存储,基本上不可能实现比这更快的单生产者单消费者队列(sans实施质量缺陷)具有先进先出顺序保证。它通常用作benchmark for all other queues。您可能想研究一下。
【讨论】:
@abc Read en.cppreference.com/w/cpp/atomic/memory_order:memory_order_acq_rel
: 具有这种内存顺序的读-修改-写操作既是获取操作又是释放操作。当前线程中的内存读取或写入不能在此存储之前或之后重新排序。
好的,很高兴知道。我想现在我已经通过谈话被“启动”了,我将能够理解 preshing 在谈论什么。以前它让我头疼。
seq_cst 在某些情况下是必要的,在这些情况下,您需要确保您的商店在您回读某些内容之前可见,以查看其他线程可能已经看到的相同视图。例如双重检查锁定是一个典型的例子。我记得提出的另一个用例是How to set bits of a bit vector efficiently in parallel?,您可以在其中回读以检查来自多个线程的冲突。
@abc 这是small reference。
同样相关的是 seq_cst 禁止 IRIW 重新排序,尽管这通常也不重要。同意你通常不需要 seq_cst。【参考方案2】:
根据 Herb Sutter 的 talk(我强烈建议您尝试理解这些内容),例如请参阅时间 36:25-45:25,OP 中给出的原子示例足以确保在增加 ptr
之前分配 a
。
这是因为递增ptr
是一个存储(因为它正在被写入),因此在给定默认内存顺序std::memory_order_seq_cst
的情况下“释放”(但也适用于*_acq_rel
或*_release
内存在这种情况下订购)。这意味着在ptr
的增量/存储/释放之前发生的任何事情都可以重新排序以在ptr
的增量/存储/释放之后发生,无论是编译器、处理器和/或缓存显式还是隐式。
除了原子加载/存储所隐含的那些之外,不需要显式的内存栅栏,实际上在讨论中不鼓励它们。
【讨论】:
以上是关于如何保证在存储发生之前加载完成?的主要内容,如果未能解决你的问题,请参考以下文章