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においては同等の機能を持つコードに置き換えられます。

/*---------------------------------------------------------------------------*
Project: CrossRoad
Copyright (C)2012-2016 Nintendo. All rights reserved.
These coded instructions, statements, and computer programs contain
proprietary information of Nintendo of America Inc. and/or Nintendo
Company Ltd., and are protected by Federal copyright law. 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.
*---------------------------------------------------------------------------*/
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