如何保证在存储发生之前加载完成?

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_acquirememory_order_release 以获取和放下队列的指针。令人高兴的是,一个无锁的一个生产者,一个消费者队列比多个生产者和/或多个消费者更简单。 @abc: "但它需要一个理解多线程和原子的博士学位才​​能实现这样的强制执行?" 问题是你正在努力思考它在“指令”和“重新排序”等方面,而不是在 C++ 线程内存模型方面。您想要 是能够在一个线程中设置一个对另一个线程可见的值。基本上你已经把它描述为一个 XY 问题。 @NicolBolas 是的,我想可能是这样。作为一个嵌入式人员,我的工作接近“金属”,所以我认为硬件实际上在做什么。我想我必须退后一步,考虑一下 C++ 置于物理执行之上的抽象。 【参考方案1】:

ptrstd::atomic&lt;int*&gt;++ptrptr++ptr.fetch_add(1, std::memory_order_acq_rel) 时,确保在此操作之前/之后没有重新排序之前/之后的加载/存储。

++ptrptr++ 本质上是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 的增量/存储/释放之后发生,无论是编译器、处理器和/或缓存显式还是隐式。

除了原子加载/存储所隐含的那些之外,不需要显式的内存栅栏,实际上在讨论中不鼓励它们。

【讨论】:

以上是关于如何保证在存储发生之前加载完成?的主要内容,如果未能解决你的问题,请参考以下文章

如何保证springmvc加载完成再加载redis

如何在页面加载完成之前显示页面加载 div?

Ionic - 在主页加载之前需要模态页面

如何确保 D3 在 javascript 运行之前完成加载多个 CSV?

类在什么时候加载和初始化

Alamofire 完成工作后如何加载视图?