Assumptions for use:
push() will only ever be called by a single thread.
pop() will only ever be called by a single thread.
Requirements:
push() needs to be as fast as possible. Requirements are stringent enough that I've determined that I can't use std::mutex (at least not in any way I've attempted to) as it is far too slow. (some initial stress tests took 6-8 ms without and 26-60 ms with std::mutex)
Code:
template<typename T>
class PCQueue {
const size_t size;
std::unique_ptr<T[]> contents;
std::atomic<size_t> head;
std::atomic<size_t> tail;
public:
explicit PCQueue(size_t s) : size(s), contents(new T[s]), head(0), tail(0) {}
//num of elements in ringbuffer (size - room())
size_t count() {
return (tail + size - head) % size;
}
//num of free spaces in ringbuffer (size - count())
size_t room() {
return (head + size - tail) % size;
}
void push(T t) {
size_t newTail = (tail + 1) % size;
if (newTail == head) {
throw std::runtime_error("Pushing Full PCQueue");
}
contents[tail] = t;
tail = newTail;//std::atomic implicitly memfences, so this op must occur after the "contents[tail] = t;"
}
T pop() {
if (tail == head) {
throw std::runtime_error("Popping Empty PCQueue");
}
T ret = contents[head];
size_t newHead = (head + 1) % size;
head = newHead;
return ret;
}
};
My testing has implied that this performs correctly (but I'd like to squeeze more speed out of it if possible), but I'm not entirely certain my assumption about the push method is correct (the comment regarding memory barriers), as I'm still getting a handle on those, and I'm not sure I understand how loads and stores can be reordered regarding atomic and non-atomic variables, with the various memory orders. sequential consistency (the default) should enforce the desired behavior I think?
I'm worried that it's just working by coincidence (as concurrent code has a tendency to do), so a verification that my code is valid would be appreciated, as well as any general tips to improve it both in readability and performance.
void pop(func<void(T& val)> consmr);
also when creating internally keep thesize+1
. \$\endgroup\$