Arrray lock free queue
#ifndef __ARRAY_LOCK_FREE_QUEUE_IMPL_MULTIPLE_PRODUCER_H__
#define __ARRAY_LOCK_FREE_QUEUE_IMPL_MULTIPLE_PRODUCER_H__
#include "atomic_ops.h"
#include <assert.h> // assert()
#include <sched.h> // sched_yield()
#include "logger_factory.h"
#include "mem_pool.h"
#include "shm_allocator.h"
/// @brief implementation of an array based lock free queue with support for
/// multiple producers
/// This class is prevented from being instantiated directly (all members and
/// methods are private). To instantiate a multiple producers lock free queue
/// you must use the ArrayLockFreeQueue fachade:
/// ArrayLockFreeQueue<int, 100, ArrayLockFreeQueue> q;
#define _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE
template <typename ELEM_T, typename Allocator = SHM_Allocator>
class ArrayLockFreeQueue
{
// ArrayLockFreeQueue will be using this' private members
template <
typename ELEM_T_,
typename Allocator_,
template <typename T, typename AT> class Q_TYPE
>
friend class LockFreeQueue;
private:
/// @brief constructor of the class
ArrayLockFreeQueue(size_t qsize = LOCK_FREE_Q_DEFAULT_SIZE);
virtual ~ArrayLockFreeQueue();
inline uint32_t size();
inline bool full();
inline bool empty();
bool push(const ELEM_T &a_data);
bool pop(ELEM_T &a_data);
/// @brief calculate the index in the circular array that corresponds
/// to a particular "count" value
inline uint32_t countToIndex(uint32_t a_count);
ELEM_T& operator[](unsigned i);
private:
size_t Q_SIZE;
/// @brief array to keep the elements
ELEM_T *m_theQueue;
/// @brief where a new element will be inserted
uint32_t m_writeIndex;
/// @brief where the next element where be extracted from
uint32_t m_readIndex;
/// @brief maximum read index for multiple producer queues
/// If it's not the same as m_writeIndex it means
/// there are writes pending to be "committed" to the queue, that means,
/// the place for the data was reserved (the index in the array) but
/// data is still not in the queue, so the thread trying to read will have
/// to wait for those other threads to save the data into the queue
///
/// note this is only used for multiple producers
uint32_t m_maximumReadIndex;
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE
/// @brief number of elements in the queue
uint32_t m_count;
#endif
private:
/// @brief disable copy constructor declaring it private
ArrayLockFreeQueue<ELEM_T, Allocator>(const ArrayLockFreeQueue<ELEM_T> &a_src);
};
template <typename ELEM_T, typename Allocator>
ArrayLockFreeQueue<ELEM_T, Allocator>::ArrayLockFreeQueue(size_t qsize):
Q_SIZE(qsize),
m_writeIndex(0), // initialisation is not atomic
m_readIndex(0), //
m_maximumReadIndex(0) //
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE
,m_count(0) //
#endif
{
m_theQueue = (ELEM_T*)Allocator::allocate(Q_SIZE * sizeof(ELEM_T));
}
template <typename ELEM_T, typename Allocator>
ArrayLockFreeQueue<ELEM_T, Allocator>::~ArrayLockFreeQueue()
{
// std::cout << "destroy ArrayLockFreeQueue\n";
Allocator::deallocate(m_theQueue);
}
template <typename ELEM_T, typename Allocator>
inline
uint32_t ArrayLockFreeQueue<ELEM_T, Allocator>::countToIndex(uint32_t a_count)
{
// if Q_SIZE is a power of 2 this statement could be also written as
// return (a_count & (Q_SIZE - 1));
return (a_count % Q_SIZE);
}
template <typename ELEM_T, typename Allocator>
inline
uint32_t ArrayLockFreeQueue<ELEM_T, Allocator>::size()
{
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE
return m_count;
#else
uint32_t currentWriteIndex = m_maximumReadIndex;
uint32_t currentReadIndex = m_readIndex;
// let's think of a scenario where this function returns bogus data
// 1. when the statement 'currentWriteIndex = m_maximumReadIndex' is run
// m_maximumReadIndex is 3 and m_readIndex is 2. Real size is 1
// 2. afterwards this thread is preemted. While this thread is inactive 2
// elements are inserted and removed from the queue, so m_maximumReadIndex
// is 5 and m_readIndex 4. Real size is still 1
// 3. Now the current thread comes back from preemption and reads m_readIndex.
// currentReadIndex is 4
// 4. currentReadIndex is bigger than currentWriteIndex, so
// m_totalSize + currentWriteIndex - currentReadIndex is returned, that is,
// it returns that the queue is almost full, when it is almost empty
//
if (countToIndex(currentWriteIndex) >= countToIndex(currentReadIndex))
{
return (currentWriteIndex - currentReadIndex);
}
else
{
return (Q_SIZE + currentWriteIndex - currentReadIndex);
}
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE
}
template <typename ELEM_T, typename Allocator>