【问题标题】:Do I need an atomic type in shared memory in this particular case?在这种特殊情况下,我是否需要共享内存中的原子类型?
【发布时间】:2018-12-31 07:47:01
【问题描述】:

我有多个使用 boost 共享内存的进程。写入器进程将写入共享内存中的数组,如下所示:

void push(int32_t val_)
{
    int nextIndex = _currentIndex.fetch_add(1,std::memory_order_relaxed);
    _buffer[nextIndex] = val_;
}
//val_ is guaranteed to be >=1
//_buffer is an array of int32_t in shared memory initialized to 0

单个阅读器进程将如下所示:

void process()
{
    int idx=0;
    while(running)
    {
      int32_t val = _buffer[idx];
      if(val)
      {
          //do some work...
          ++idx;
      }
    }
}            

根据提升: “该地址范围内的更改会被同样映射相同共享内存对象的其他进程自动看到。”

我的问题是,假设 _buffer 正确对齐, _buffer 可以简单地是 int32_t 的数组,还是绝对有必要将 _buffer 定义为 std::atomic 的数组?在 x86 上写入 int32_t 是原子的,假设对齐是正确的,并且 boost 保证其他进程会看到更新。

CPU 信息:

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                24
On-line CPU(s) list:   0-23
Thread(s) per core:    1
Core(s) per socket:    12
Socket(s):             2
NUMA node(s):          2
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 63
Stepping:              2
CPU MHz:               2596.945
BogoMIPS:              5193.42
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              30720K
NUMA node0 CPU(s):     0-5,12-17
NUMA node1 CPU(s):     6-11,18-23

【问题讨论】:

    标签: c++ shared-memory atomic


    【解决方案1】:

    TL;DR:即便如此,您确实需要同步您的访问。并使用原子,而不是普通的互斥锁。

    这里有几个问题:

    首先,您有一个写入线程和一个读取线程,写入和读取相同的内存位置。这意味着必须保护对该位置的访问(使用锁、原子操作、栅栏等)。这些线程位于不同进程中的事实不会影响它。您仍然需要解决数据竞争。

    其次,尽管 Boost 文档说映射该区域的其他进程会自动看到更改,但我相信这只是一种简化。共享内存库不能为不同进程之间的共享内存提供比同一进程中不同线程存在的更强大的保证。所有相同的问题仍然可能出现:您的读/写可能会被编译器或 CPU 甚至内存系统重新排序,甚至完全省略或与其他读/写结合。并且有缓存和 MMU 影响需要考虑。

    因此,即使您的数据已正确对齐,并且对该数据类型的写入在您的架构上是原子的,但如果您不保护您的访问,它不会提供任何安全措施来防止数据竞争导致的不正确行为。这里没有魔法;如果您在拥有线程时必须同步/保护/原子化您的访问,那么您也需要对进程执行相同的操作。您可能需要做的更多。

    第三,适用于同一进程内线程的同步原语可能不会(也可能不会)跨不同进程工作。原子操作(或 C++ 实现的类型)确实有效。内存栅栏也可能起作用,具体取决于您在做什么。虽然 IIRC,Boost 的共享内存库提供了跨进程边界工作的特殊同步原语(如果放置在共享区域内)。

    【讨论】:

    • 理论上 boost 可以同步两个进程中对该共享缓冲区的所有访问。这就是文档所暗示的。我怀疑文档在撒谎,因为那太贵了。
    【解决方案2】:

    正如你自己写的那样,一个线程写入某个内存位置:

    _buffer[nextIndex] = val_;
    

    另一个线程读取该内存位置:

      int32_t val = _buffer[idx];
    

    根据标准,该内存地址必须同步,否则它是未定义的行为。

    该数组必须是原子数组、受互斥体或快速自旋锁保护的简单数组或任何其他其读写同步的数组。

    【讨论】:

    • 我不认为标准的 C++ 同步原语(除原子之外)可以跨进程边界工作。我弄错了吗?
    • @David - 我想我的假设是即使标准说它是 UB,我的架构说它是原子的,这在这种情况下很重要。我也不认为代码将被重新排序(可以确认 asm),因为它也会改变单线程行为。
    猜你喜欢
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多