在这种情况下,共享内存中是否需要原子类型?

时间:2018-07-23 19:23:11

标签: c++ shared-memory atomic atomicity

我有多个使用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进行写入是原子的。

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

2 个答案:

答案 0 :(得分:1)

在您编写自己的代码时,一个线程将写入某些内存位置:

_buffer[nextIndex] = val_;

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

  int32_t val = _buffer[idx];

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

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

答案 1 :(得分:0)

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

这里有一些问题:

首先,您有一个写程序线程和一个读程序线程,分别在同一内存位置进行读写操作。这意味着必须保护访问该位置的权限(使用锁,原子操作,围栏,等等。)这些线程处于不同进程中的事实并没有受到影响。您仍然需要解决数据争用问题。

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

因此,即使您的数据正确对齐,并且在该体系结构上写入该数据类型是原子的,但如果您不保护访问,它就无法防止由于数据争用而导致的不正确行为。这里没有魔术;如果您在拥有线程时必须同步/保护/以原子方式访问,那么您也需要对进程进行相同的操作。您可能需要做更多的事情。

第三,为同一进程内的线程工作的同步原语可能无法(也可能不会)跨不同进程工作。原子操作(或类型,如C ++实现的那样)有效。内存防护可能也会起作用,具体取决于您正在执行的操作。但是,在IIRC中,Boost的共享内存库提供了特殊的同步原语,这些原语跨进程边界(如果放置在共享区域内)。