如何在其他线程中“快速”显示一个线程中的内存存储?

时间:2017-02-02 13:43:23

标签: c++ multithreading c++11 atomic stdatomic

假设我想将设备寄存器的内容复制到一个可由多个线程读取的变量中。这样做有一个很好的一般方法吗?以下是两种可能的方法示例:

#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的处理器核心数。
  • 每个核心都有大量的寄存器。
  • 应用程序足够小,可以在构建可执行文件时成功使用整个程序优化。

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

2 个答案:

答案 0 :(得分:2)

如果您想以原子方式更新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);。在这种情况下,读者需要将device_reg_copy加载为std::memory_order_acquire。同样,在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 Cache Flushing Fallacy文章。

答案 1 :(得分:0)

C ++标准对于使其他线程可以看到原子库来说相当模糊。

  

12年3月29日   实现应该使原子库在合理的时间内对原子载荷可见。

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

使用独立的栅栏强制执行某个内存排序是不必要的,因为您可以在原子操作上指定它们,但问题是, 您对使用记忆围栏的期望是什么?
Fences旨在强制执行内存操作(线程之间)的排序,但它们不保证以及时方式进行可见性。 您可以将值存储到具有最强内存排序的原子变量(即seq_cst),但即使另一个线程在load()之后的时间执行store(), 你可能仍然会从缓存中获得一个旧值但是(令人惊讶的是)违反了发生在之前的关系。 使用更强大的围栏可能会产生差异。时间和可见度,但没有保证。

如果提示可见性很重要,我会考虑使用读 - 修改 - 写(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写入的值,则保证在线程2中加载之后,线程1中存储之前的内存操作可见。 获取/释放操作形成一对,并根据存储和加载之间的运行时关系进行同步。换句话说,如果线程2没有看到线程1存储的值, 没有订购保证。

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

正如其他人所说,在与volatile进行线程间通信时,不需要std::atomic