My header file implementation:
#ifndef CIRCULARSPSC_H
#define CIRCULARSPSC_H
#include <atomic>
#include <cstddef>
template<typename Data, size_t Size>
class CircularSpsc
{
public:
enum { queue_size = Size + 1 };
CircularSpsc() : back(0), front(0){}
virtual ~CircularSpsc() {}
bool push_back(const Data& item) ;
bool pop_front(Data& item);
bool isEmpty() const;
bool isFull() const;
private:
size_t increment(size_t idx) const;
std::atomic<size_t> back;
Data dataArray[queue_size];
std::atomic<size_t> front;
};
#include "circularspsc.cpp"
#endif
My source file implementation:
#ifndef CIRCULARSPSC_CPP
#define CIRCULARSPSC_CPP
#include "circularspsc.hpp"
template<typename Data, size_t Size>
bool CircularSpsc<Data, Size>::push_back(const Data& item)
{
const auto current_back = back.load(std::memory_order_relaxed);
const auto next_back = increment(current_back);
if(next_back != front.load(std::memory_order_acquire))
{
dataArray[current_back] = item;
back.store(next_back, std::memory_order_release);
return true;
}
return false;
}
template<typename Data, size_t Size>
bool CircularSpsc<Data, Size>::pop_front(Data& item)
{
const auto currentfront = front.load(std::memory_order_relaxed);
if(currentfront == back.load(std::memory_order_acquire))
return false;
item = dataArray[currentfront];
front.store(increment(currentfront), std::memory_order_release);
return true;
}
template<typename Data, size_t Size>
bool CircularSpsc<Data, Size>::isEmpty() const
{
return (front.load() == back.load());
}
template<typename Data, size_t Size>
bool CircularSpsc<Data, Size>::isFull() const
{
const auto nextback = increment(back.load());
return (nextback == front.load());
}
template<typename Data, size_t Size>
size_t CircularSpsc<Data, Size>::increment(size_t idx) const
{
return (idx + 1) % queue_size;
}
#endif
Client file implementation:
#include <iostream>
#include <thread>
#include <vector>
#include "circularspsc.hpp"
#include "dataobject.hpp"
#define QUEUESIZE 10000
void reader(CircularSpsc<int, QUEUESIZE>& cspsc_q)
{
for(int i = 0; i < QUEUESIZE; i++)
{
int dataItem;
cspsc_q.pop_front(dataItem);
std::cout << "popped value: " << dataItem << std::endl;
}
}
void writer(CircularSpsc<int, QUEUESIZE>& cspsc_q)
{
for(int i = 0; i < QUEUESIZE; i++)
{
cspsc_q.push_back(i);
}
}
int main()
{
CircularSpsc<int, QUEUESIZE> cspsc_q;
std::thread writer_thread(writer, std::ref(cspsc_q));
std::thread reader_thread(reader, std::ref(cspsc_q));
reader_thread.join();
writer_thread.join();
}
This has the clear drawback that we can only create queues of very limited size (because with larger values of QUEUESIZE I am getting segfault (surely because the stack size is limited?)). In my next iteration I plan to implement a version with dynamic memory allocation inside the constructor of the queue for QUEUESIZE number of objects and then use placement new to move values inside the queue.