nlib
misc/threading/simpleringbuffer/simpleringbuffer.cpp

2スレッドの間だけで読み書きできるロックを使わないリングバッファのサンプルです。

nn::nlib::threading::SimpleRingBufferはクラステンプレートで、リングバッファに格納する型Tと、リングバッファのサイズNをテンプレート引数に指定するようになっています。

1:1のスレッドの場合ならば、このクラスを利用することによってロックを利用せずにデータを安全にやりとりすることができます。

サンプルでは0から99までの整数を別スレッドが生成しリングバッファにプッシュします。

メインスレッドはリングバッファから値を100回ポップして値が順番通りかチェックします。

SimpleRingBufferクラスではメモリバリアに、 NLIB_MEMORY_ORDER_ACQUIREマクロと NLIB_MEMORY_ORDER_RELEASEマクロを利用しています。
これらは、C++11環境ではそれぞれatomic_thread_fence(memory_order_acquire)及びatomic_thread_fence(memory_order_release)に置換されます。
C++03や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() : m_Head(0), m_Tail(0) {}
bool TryPush(const T& value);
bool TryPop(T* value);
private:
uint32_t next(uint32_t current) { return (current + 1) % N; }
int32_t m_Head;
int32_t m_Tail;
T m_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(&m_Head, NLIB_ATOMIC_RELAXED);
int32_t next_head = this->next(head);
int32_t tail = nlib_atomic_load32(&m_Tail, NLIB_ATOMIC_ACQUIRE);
if (next_head == tail) return false;
m_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(&m_Tail, NLIB_ATOMIC_RELAXED);
int32_t head = nlib_atomic_load32(&m_Head, NLIB_ATOMIC_ACQUIRE);
if (tail == head) return false;
*value = m_Ring[tail];
nlib_atomic_store32(&m_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