我实现了单生产者 - 单一消费者无锁循环缓冲区。参考实现是: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标志。
答案 0 :(得分:3)
由于问题仅在您删除volatile
时发生,因此问题很可能是由编译器优化引起的,该优化会优化读取器线程中m_wIndex的读取。编译器认为它知道可能改变该值的所有内容,因此没有理由不止一次从内存中读取它。
添加对fprintf的调用会使这种优化失败,因为fprintf可能会修改该变量。
添加j
会使您的代码更加复杂,并且似乎也会使优化失败。