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.

/*--------------------------------------------------------------------------------*
Project: CrossRoad
Copyright (C)Nintendo All rights reserved.
These coded instructions, statements, and computer programs contain proprietary
information of Nintendo and/or its licensed developers and are protected by
national and international copyright laws. They may not be disclosed to third
parties or copied or duplicated in any form, in whole or in part, without the
prior written consent of Nintendo.
The content herein is highly confidential and should be handled accordingly.
*--------------------------------------------------------------------------------*/
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