奇怪的循环缓冲行为

时间:2015-09-11 14:45:48

标签: c++ multithreading circular-buffer

我实现了单生产者 - 单一消费者无锁循环缓冲区。参考实现是: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关键字的读取和写入索引整个事情就像一个魅力。 Consumer循环终止,生产者返回。

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

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

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编译代码,并在运行Ubuntu的64位intel(i3)机器上设置-O3标志。

1 个答案:

答案 0 :(得分:3)

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

添加对fprintf的调用会使这种优化失败,因为fprintf可能会修改该变量。

添加j会使您的代码更加复杂,并且似乎也会使优化失败。