#ifndef MULTIQUEUE_HPP_
#define MULTIQUEUE_HPP_
#include <atomic>
#include <condition_variable>
#include <stdexcept>
#include <vector>
template<size_t cachealignlinesize = 64>
class multiqueue_cacheline{
const int producersinthread, consumers;outthread;
const size_t ringSizeringsize, unblockProducerThresholdunblockproducer, unblockConsumerThreshold;unblockconsumer;
struct referenceref{
std::atomic<int> count;v;
bool eof;
char padding[cachealignpadding[linesize > sizeof(countv) + sizeof(eof) ? cachealignlinesize - sizeof(countv) - sizeof(eof) : 0];
referenceref(int v): countv(v), eof(false) {}
};
static_assertref(cachealign, "cachealign mustconst beref& nono): zerov(o."v.load(std::memory_order_relaxed);
), eof(false) {}
reference* refs;
};
void* unalignedRing =std::vector<ref> 0;refs;
std::atomic<size_t> inRinginring = {0};
struct blockersleepobj{
std::condition_variable condition;cond;
std::mutex mutex;m;
template<typename T>
void sleep(T& ready){
std::unique_lock<std::mutex> lk(mutexm);
if(!ready()) conditioncond.wait(lk, ready);
}
void wake(){
std::unique_lock<std::mutex> lk(mutexm);
conditioncond.notify_all();
}
} consumerLockconsumersleep, producerLock;producersleep;
public:
multiqueue_cacheline(unsigned int producersinthread, unsigned int consumersoutthread, size_t ringSizeringsize, size_t unblockProducerThresholdunblockproducer = 0, size_t unblockConsumerThresholdunblockconsumer = 0)
: producersinthread(producersinthread), consumersoutthread(consumersoutthread), ringSizeringsize(ringSizeringsize), unblockProducerThresholdunblockproducer(unblockProducerThresholdunblockproducer ? unblockProducerThresholdunblockproducer : ringSizeringsize / 5), unblockConsumerThresholdunblockconsumer(unblockConsumerThresholdunblockconsumer ? unblockConsumerThresholdunblockconsumer : (ringSizeringsize * 4) / 5)
{
if(!producersinthread || !consumersoutthread || !(ringSizeringsize > this->unblockConsumerThreshold>unblockconsumer && this->unblockConsumerThreshold>unblockconsumer > this->unblockProducerThreshold>unblockproducer && this->unblockProducerThreshold>unblockproducer > 0))
throw std::invalid_argument("Bad ring parameters.");
unalignedRingrefs = malloc(sizeof(reference[ringSize]) + cachealign - 1);
if(!unalignedRing) throw std::bad_alloc();
refs = reinterpret_cast<reference*>((size_t(unalignedRing) + cachealign - 1) & ~(cachealign - 1));
forvector<ref>(size_t i = 0; i < ringSize; i++)ringsize, new{int(refs + iinthread) reference(producers});
std::atomic_thread_fence(std::memory_order_release);
}
~multiqueue_cacheline(){
free(unalignedRing);
}
ssize_t acquire_consumer(size_t i){
auto ready = [=]{ return refs[i].countv.load(std::memory_order_acquire) <= 0 || refs[i].eof; };
if(!ready()) consumerLockconsumersleep.sleep(ready);
if(refs[i].eof){
producerLockproducersleep.wake();
return -1;
}
return i;
};
void release_consumer(size_t& i){
if(refs[i].countv.fetch_sub(1, std::memory_order_acq_rel) == 1 - consumersoutthread){
refs[i].countv.store(producersinthread, std::memory_order_release);
if(inRinginring.fetch_sub(1, std::memory_order_relaxed) == unblockProducerThresholdunblockproducer + 1) producerLockproducersleep.wake();
}
i = (i + 1) % ringSize;ringsize;
};
size_t acquire_producer(size_t i){
auto ready = [=]{ return refs[i].countv.load(std::memory_order_acquire) > 0; };
if(!ready()) producerLockproducersleep.sleep(ready);
return i;
};
void release_producer(size_t& i){
if(refs[i].countv.fetch_sub(1, std::memory_order_acq_rel) == 1 && inRinginring.fetch_add(1, std::memory_order_relaxed) == unblockConsumerThresholdunblockconsumer - 1)
consumerLockconsumersleep.wake();
i = (i + 1) % ringSize;ringsize;
};
void eof(size_t i){
acquire_producer(i);
refs[i].eof = true;
release_producer(i);
consumerLockconsumersleep.wake();
};
};
using multiqueue = multiqueue_cacheline<>;
using multiqueue_nocachealignmultiqueue_nocacheline = multiqueue_cacheline<1>;multiqueue_cacheline<0>;
#endif /* MULTIQUEUE_HPP_ */
The queue constructor is multiqueue::multiqueue(unsigned int producersinthread, unsigned int consumersoutthread, size_t ringsize, size_t unblockProducerThresholdunblockproducer = 0, size_t unblockConsumerThresholdunblockconsumer = 0)
, where besides the obvious parameters
unblockProducerThresholdunblockproducer
indicates when the ring is "almost empty", so if too fast producers were previously blocked they should be awaken, andunblockConsumerThresholdunblockconsumer
the opposite situation in case that consumers were blocked for starvation.