我有一个 RingBuffer,它为一个消费者和一个生产者提供服务,并使用两个整数来检测新数据:
_lastReadIndex
_lastWrittenIndex
所以当这两个值不相等时,ringbuffer 中有未读数据。
当一个项目被添加到环形缓冲区时,生产者递增(和循环缓冲区大小的模数)_lastWrittenIndex
。
消费者自旋,读取两个值,检查新数据,当有新数据时,它将递增(和模数)_lastReadIndex
。
三个突出显示的术语强调了关于多线程和内存屏障的要求。
考虑到 Intel 的内存模型,我可以将此设计的内存排序放宽到什么程度?我相信英特尔的内存模型允许加载与早期存储重新排序到不同的地址?
使用 C++11 原子库 std::memory_order_xxxx
等进行编辑
最佳答案
在做任何事情之前你必须做的几件事:
模数读取和写入点,但保持 _lastReadIndex
和 _lastWrittenIndex
不变,以了解您有多少数据可用,丢失了多少,或者可能会阻塞 writer 如果它在整个周期后超过了阅读器。
而且,非常重要的是,尽可能避免共享 - 将读取器变量和写入器变量放在不同的缓存行上。
现在,回答你的问题:
如果您想要实现可移植性,那么您在代码中需要的内存顺序不应考虑架构。标准的原子函数可以解决这个问题。 在递增写入索引之前,您只需要确保缓冲区中的数据可用,这意味着递增时释放语义。 您还必须确保编写器将数据写入内存,而不是优化为仅保留在寄存器中。
newIndex = _lastWrittenIndex+1;
buffer[newIndex % bufSize] = newData;
atomic_store( &_lastWrittenIndex, newIndex, memory_order_release );
在 x86/64 上,这与:
newIndex = _lastWrittenIndex+1;
buffer[newIndex % bufSize] = newData;
// release semantics means reorder barrier before action:
barrier(); // translates to `asm volatile("":::"memory");`
*(volatile int*)_lastWrittenIndex = newIndex;
当编写访问 _lastWrittenIndex
的代码时,如上所示,您最好将其声明为 volatile,但请记住仍然需要屏障!
关于c++ - 单一生产者、单一消费者环形缓冲区的最小限制内存排序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34967350/