I'm playing with the std::atomic structures and wrote this lock-free multi-producer multi-consumer queue, which I'm attaching here. The idea for the queue is based on two stacks - a producer and consumer stack, which are essentially linked list structures. The nodes of the lists hold the indexes into an array of that holds the actual data, where you would read or write.
The idea would be that the nodes for the lists are mutually exclusive, ie, a pointer to a node can exist only in the producer or the consumer list. A producer would attempt to acquire a node from the producer list, a consumer from the consumer list and whenever a pointer to a node is acquired by either producer or consumer, it should be out of both lists so that noone else could acquire it. I'm using std::atomic_compare_exchange functions to spin until a node is popped.
The problem is that there must be something wrong with the logic or the operations are not atomic as I assume them to be because even with 1 producer and 1 consumer, given enough time, the queue will livelock and what I have noticed is that if you assert that cell != cell->m_next, the assert would get hit ! So its probably something staring me in the face and I just don't see it, so I wonder if someone could pitch in.
Thx
#ifndef MTQueue_h
#define MTQueue_h
#include <atomic>
template<typename Data, uint64_t queueSize>
class MTQueue
{
public:
MTQueue() : m_produceHead(0), m_consumeHead(0)
{
for(int i=0; i<queueSize-1; ++i)
{
m_nodes[i].m_idx = i;
m_nodes[i].m_next = &m_nodes[i+1];
}
m_nodes[queueSize-1].m_idx = queueSize - 1;
m_nodes[queueSize-1].m_next = NULL;
m_produceHead = m_nodes;
m_consumeHead = NULL;
}
struct CellNode
{
uint64_t m_idx;
CellNode* m_next;
};
bool push(const Data& data)
{
if(m_produceHead == NULL)
return false;
// Pop the producer list.
CellNode* cell = m_produceHead;
while(!std::atomic_compare_exchange_strong(&m_produceHead,
&cell, cell->m_next))
{
cell = m_produceHead;
if(!cell)
return false;
}
// At this point cell should point to a node that is not in any of the lists
m_data[cell->m_idx] = data;
// Push that node as the new head of the consumer list
cell->m_next = m_consumeHead;
while (!std::atomic_compare_exchange_strong(&m_consumeHead,
&cell->m_next, cell))
{
cell->m_next = m_consumeHead;
}
return true;
}
bool pop(Data& data)
{
if(m_consumeHead == NULL)
return false;
// Pop the consumer list
CellNode* cell = m_consumeHead;
while(!std::atomic_compare_exchange_strong(&m_consumeHead,
&cell, cell->m_next))
{
cell = m_consumeHead;
if(!cell)
return false;
}
// At this point cell should point to a node that is not in any of the lists
data = m_data[cell->m_idx];
// Push that node as the new head of the producer list
cell->m_next = m_produceHead;
while(!std::atomic_compare_exchange_strong(&m_produceHead,
&cell->m_next, cell))
{
cell->m_next = m_produceHead;
}
return true;
};
private:
Data m_data[queueSize];
// The nodes for the two lists
CellNode m_nodes[queueSize];
volatile std::atomic<CellNode*> m_produceHead;
volatile std::atomic<CellNode*> m_consumeHead;
};
#endif