c++ - 单一生产者、单一消费者环形缓冲区的最小限制内存排序?

标签 c++ multithreading c++11 memory-barriers

我有一个 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/

相关文章:

c++ - Code::blocks 提示参数类型

c++ - 简单数组导致异常

c++ - LLVM:将全局变量链接类型设为 linkonce

c# - 不支持 STA 线程上多个句柄的 WaitAll

android - 运行具有运行时限制 android 的新线程

c++ - 制作可变参数模板类的 forward_list

c++ - 何时使用 std::begin 和 std::end 而不是容器特定版本

c++ - 无法使用嵌套的 lambda 捕获静态成员

c++ - 在 gcc 和 Visual C++ 中使用 '>>'

python - Django Rest Framework 线程安全吗?