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;
template <class T, size_t N>
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];
};
template <class T, size_t N>
bool SimpleRingBuffer<T, N>::TryPush(const T& value) {
int32_t next_head = this->next(head);
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;
if (tail == head) return false;
*value = m_Ring[tail];
return true;
}
SimpleRingBuffer<int, 10> g_Buffer;
Thread g_Th;
void ThreadFunc() {
for (int i = 0; i < 100; ++i) {
while (!g_Buffer.TryPush(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();
for (int i = 0; i < 100; ++i) {
int val;
while (!g_Buffer.TryPop(&val)) {
}
if (val != i) return false;
}
return true;
}
static bool SampleMain(int, char**) { return SimpleRingBufferDemo(); }
NLIB_MAINFUNC