如何使一个线程中的内存存储"promptly"在其他线程中可见?

How do I make memory stores in one thread "promptly" visible in other threads?

本文关键字:线程 其他 promptly 存储 何使一 内存      更新时间:2023-10-16

假设我想将设备寄存器的内容复制到一个将由多个线程读取的变量中。 有没有一个好的通用方法可以做到这一点? 以下是执行此操作的两种可能方法的示例:

#include <atomic>
volatile int * const Device_reg_ptr = reinterpret_cast<int *>(0x666);
// This variable is read by multiple threads.
std::atomic<int> device_reg_copy;
// ...
// Method 1
const_cast<volatile std::atomic<int> &>(device_reg_copy)
.store(*Device_reg_ptr, std::memory_order_relaxed);
// Method 2
device_reg_copy.store(*Device_reg_ptr, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);

更一般地说,面对可能的整个程序优化,如何正确控制一个线程中的内存写入延迟在其他线程中可见?

编辑:在您的回答中,请考虑以下情况:

  • 代码在嵌入式系统的 CPU 上运行。
  • CPU 上运行单个应用程序。
  • 应用程序的线程数远远少于 CPU 的处理器内核。
  • 每个内核都有大量的寄存器。
  • 该应用程序足够小,因此在构建其可执行文件时可以成功使用整个程序优化。

我们如何确保一个线程中的存储不会无限期地对其他线程不可见?

如果您想以原子方式更新device_reg_copy的值,那么device_reg_copy.store(*Device_reg_ptr, std::memory_order_relaxed);就足够了。

没有必要将volatile应用于原子变量,这是不必要的。

std::memory_order_relaxed存储应该产生最少的同步开销。在x86上,它只是一个普通的mov指令。

但是,如果您想以这样的方式更新它,以使任何先前存储的效果与新值device_reg_copy一起对其他线程可见,请使用std::memory_order_release存储,即device_reg_copy.store(*Device_reg_ptr, std::memory_order_release);.在这种情况下,读取器需要按std::memory_order_acquire加载device_reg_copy。同样,在x86上std::memory_order_release商店是一个普通的mov

而如果您使用最昂贵的std::memory_order_seq_cst存储,它确实会在x86上为您插入内存屏障。

这就是为什么他们说 x86 内存模型对于 C++11 来说有点太强了:普通mov指令在存储上std::memory_order_release,在负载上std::memory_order_acquire。x86 上没有轻松的存储或负载。

我不能推荐足够的CPU缓存刷新谬误文章。

C++标准在使原子存储对其他线程可见方面相当模糊。

12-3-29 实现应该在合理的时间内使原子存储对原子负载可见。

这是非常详细的,没有"合理"的定义,也不必立即定义。

使用独立围栏来强制特定的内存排序是不必要的,因为您可以在原子操作上指定这些顺序,但问题是, 您对使用内存围栏的期望是什么..
栅栏旨在对内存操作(线程之间)强制排序,但它们不能保证及时的可见性。 您可以将值存储到具有最强内存顺序的原子变量(即。seq_cst),但即使另一个线程在比store()更晚的时间执行load(), 您可能仍然会从缓存中获取旧值,但(令人惊讶的是)它并没有违反发生前关系。 使用更坚固的围栏可能会有所作为。时间和可见性,但无法保证。

如果提示可见性很重要,我会考虑使用读取-修改-写入 (RMW) 操作来加载值。 这些是以原子方式读取和修改的原子操作(即在单个调用中),并具有保证对最新值进行操作的附加属性。 但是,由于它们必须到达比本地缓存更远的地方,因此这些调用的执行成本也往往更高。

正如Maxim Egorushkin所指出的,你是否可以使用比默认值(seq_cst)弱的内存排序取决于其他内存操作是否需要在线程之间同步(可见)。 从您的问题中不清楚,但通常认为使用默认值(顺序一致性)是安全的。
如果你在一个异常弱的平台上,如果性能有问题,如果你需要在线程之间进行数据同步,你可以考虑使用获取/释放语义:

// thread 1
device_reg_copy.store(*Device_reg_ptr, std::memory_order_release);

// thread 2
device_reg_copy.fetch_add(0, std::memory_order_acquire);

如果线程 2 看到线程 1写入的值,则可以保证线程 1 中存储之前的内存操作在线程 2 中加载后可见。 获取/发布操作形成一对,它们根据存储和负载之间的运行时关系进行同步。换句话说,如果线程 2 看不到线程 1 存储的值, 没有订购保证。

如果原子变量不依赖于任何其他数据,则可以使用std::memory_order_relaxed;存储顺序始终保证单个原子变量。

正如其他人提到的,在与std::atomic的线程间通信方面不需要volatile