nlib
misc/threading/simpleringbuffer/simpleringbuffer.cpp

Sample ring buffer that allows read/write operations between two threads without a lock.

nn::nlib::threading::SimpleRingBuffer is a class template. It is written to specify the type T to store in the ring buffer, and the size of the ring buffer N as a template argument.

In the case of a 1:1 thread, using this class allows for data to be exchanged safely without using a lock.

This sample shows a thread generating integers from 0 to 99, and pushing it to the ring buffer.

The main thread pops a value from the ring buffer 100 times, and checks whether the values are in the correct order.

The SimpleRingBuffer class uses the NLIB_MEMORY_ORDER_ACQUIRE and NLIB_MEMORY_ORDER_RELEASE macros for the memory barrier.
These macros are replaced with atomic_thread_fence(memory_order_acquire) and atomic_thread_fence(memory_order_release) respectively in a C++11 environment.
This function is replaced with equivalent code in C++03 or C.

using ::nlib_ns::threading::Thread;
// You can push/pop the ring buffer with lock free / wait free.
// Only available for the case such that PRODUCER : CONSUMER = 1 : 1
template <class T, size_t N>
class SimpleRingBuffer NLIB_FINAL {
public:
SimpleRingBuffer() : head_(0), tail_(0) {}
bool TryPush(const T& value);
bool TryPop(T* value);
private:
uint32_t next(uint32_t current) { return (current + 1) % N; }
int32_t head_;
int32_t tail_;
T ring_[N];
NLIB_DISALLOW_COPY_AND_ASSIGN(SimpleRingBuffer);
};
template <class T, size_t N>
bool SimpleRingBuffer<T, N>::TryPush(const T& value) {
int32_t head = nlib_atomic_load32(&head_, NLIB_ATOMIC_RELAXED);
int32_t next_head = this->next(head);
int32_t tail = nlib_atomic_load32(&tail_, NLIB_ATOMIC_ACQUIRE);
if (next_head == tail) return false;
ring_[head] = value;
return true;
}
template <class T, size_t N>
bool SimpleRingBuffer<T, N>::TryPop(T* value) {
if (!value) return false;
int32_t tail = nlib_atomic_load32(&tail_, NLIB_ATOMIC_RELAXED);
int32_t head = nlib_atomic_load32(&head_, NLIB_ATOMIC_ACQUIRE);
if (tail == head) return false;
*value = ring_[tail];
nlib_atomic_store32(&tail_, this->next(tail), NLIB_ATOMIC_RELEASE);
return true;
}
SimpleRingBuffer<int, 10> g_Buffer;
Thread g_Th;
void ThreadFunc() {
// push 0, .... 99 to the ring buffer
for (int i = 0; i < 100; ++i) {
// fails immedidately when the ring buffer is full.
while (!g_Buffer.TryPush(i)) {
nlib_yield(); // CTR needs it....
}
nlib_printf("Pushed %d into the ring buffer\n", i);
}
}
bool SimpleRingBufferDemo() {
nlib_printf("One thread pushes [0...99] into the ring buffer,\n");
nlib_printf("while the other thread pops numbers from the same buffer without any lock.\n");
nlib_printf("This works well with memory barrier.\n\n");
g_Th.Start(ThreadFunc);
g_Th.Detach();
// pop from the ring buffer, the sequence should be 0, .... 99
for (int i = 0; i < 100; ++i) {
int val;
// fails immedidately when the ring buffer is full.
while (!g_Buffer.TryPop(&val)) {
nlib_yield(); // CTR needs it....
}
nlib_printf(" Popped %d from the ring buffer\n", val);
if (val != i) return false;
}
return true;
}
static bool SampleMain(int, char**) { return SimpleRingBufferDemo(); }
NLIB_MAINFUNC