奇怪的循环缓冲区行为

Strange Circular Buffer behaviour

我实现了一个单生产者单消费者无锁循环缓冲区。参考实现位于:http://home.comcast.net/~lang.dennis/code/ring/ring.html(基线实现,从顶部开始的第一个列表)。

编辑: 参考原代码:

template <class T, size_t RingSize>
class RingBuffer
{
public:
RingBuffer(size_t size = 100) 
    : m_size(size), m_buffer(new T[size]), m_rIndex(0), m_wIndex(0) 
    { assert(size > 1 && m_buffer != NULL); }

~RingBuffer() 
    { delete [] m_buffer; };

size_t Next(size_t n) const 
    { return (n+1)%m_size; }
bool Empty() const 
    { return (m_rIndex == m_wIndex); }
bool Full() const
    { return (Next(m_wIndex) == m_rIndex); }

bool Put(const T& value)
{
    if (Full()) 
        return false;
    m_buffer[m_wIndex] = value;
    m_wIndex = Next(m_wIndex);
    return true;
}

bool Get(T& value)
{
    if (Empty())
        return false;
    value = m_buffer[m_rIndex];
    m_rIndex = Next(m_rIndex);
    return true;
}

private:
T*              m_buffer;
size_t          m_size;

// volatile is only used to keep compiler from placing values in registers.
// volatile does NOT make the index thread safe.
volatile size_t m_rIndex;   
volatile size_t m_wIndex;
};

Mod:我在局部变量中存储读写索引,在表达式中只使用局部变量。我在从函数(get 和 put)返回之前更新它们。

为清楚起见,get 函数:

bool Get(T& value)
 {
    size_t w=m_wIndex;
    size_t r=m_rIndex;
    if (Empty(w,r))
        return false;
    value = m_buffer[r];
    //just in case the compiler decides to be extra smart
    compilerbarrier();
    m_rIndex = Next(r);
    return true;
 }

然后,我创建了单独的生产者和消费者线程:

生产者线程循环:

uint64_t i = 0;
while (i <= LOOPS) {
 if (buf.put(i)) {
  i += 1;
 }
}
consumerthread.join(); //pseudocode: wait for the consumer to finish

消费者线程循环:

uint64_t i=0;
while (i < LOOPS) {
 buf.get(i);
}

生产者将整数[0,LOOPS]放入缓冲区,消费者从缓冲区中一次取一个,直到最后得到整数值LOOPS,消费者循环终止。请注意缓冲区的大小远小于 LOOPS.

如果我为读取和写入索引保留 volatile 关键字,那么整个工作就像一个魅力。消费者循环终止,生产者 returns.

但是,如果我删除 volatile 关键字,那么消费者永远不会 returns。

奇怪的是,这个消费者循环终止了:

uint64_t i=0;
while (i < LOOPS) {
 buf.get(i);
 fprintf(stderr,"%lu\n",i);
}

这个也终止了:

uint64_t i=0, j=0;
while (j < LOOPS) {
    if(buf.get(i)) {
     j=i;
    }
}

这是怎么回事?我使用 gcc 4.8.4 编译了代码,在 64 位英特尔 (i3) 机器上设置了 -O3 标志 运行 Ubuntu.

由于该问题仅在您删除 volatile 时出现,该问题很可能是由编译器优化引起的,该优化优化了 reader 线程中对 m_wIndex 的读取。编译器认为它知道可能改变该值的所有内容,因此没有理由多次从内存中读取它。

添加对 fprintf 的调用会破坏此优化,因为 fprintf 可能会修改该变量。

添加 j 使您的代码更加复杂,而且似乎也破坏了优化。