北川广海の梦

北川广海の梦

Ringbuffer环形队列

2023-06-20
Ringbuffer环形队列

RingBuffer

环形缓冲队列,在系统中是非常常见的数据结构,许多场景都是用到了它,例如大名鼎鼎的Linux io_uring,它通过在共享内存中使用环形队列,避免了锁与内核切换的开销,大大提高性能。再比如Golang语言中的channel,在有缓冲的情况下,其内部实现也是通过环形队列的。不过它并非无锁,因为在channel的场景中,必定会面临多个生产者和多个消费者的情况,必须保证线程安全。

简易实现

package ringbuffer

type RingBuffer[T any] struct {
	size   uint32
	buffer []T
	head   uint32
	tail   uint32
}

func NewRingBuffer[T any](size uint32) *RingBuffer[T] {
	buffer := &RingBuffer[T]{
		size:   size + 1,
		buffer: make([]T, size+1),
		head:   0,
		tail:   0,
	}
	return buffer
}

func (r *RingBuffer[T]) Empty() bool {
	return r.head == r.tail
}

func (r *RingBuffer[T]) Full() bool {
	return r.head == (r.tail+1)%r.size
}

func (r *RingBuffer[T]) Get() (t T, exist bool) {
	if r.Empty() {
		return
	}
	t = r.buffer[r.head]
	r.head = (r.head + 1) % r.size
	return t, true
}

func (r *RingBuffer[T]) Put(t T) (ok bool) {
	if r.Full() {
		return false
	}
	r.buffer[r.tail] = t
	r.tail = (r.tail + 1) % r.size
	return true
}

单元测试

package ringbuffer

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestPut(t *testing.T) {
	buffer := NewRingBuffer[string](3)

	cases := []struct {
		val string
		ok  bool
	}{
		{val: "one", ok: true},
		{val: "two", ok: true},
		{val: "three", ok: true},
		{val: "four", ok: false},
	}
	for _, ca := range cases {
		ok := buffer.Put(ca.val)
		if ok != ca.ok {
			t.Errorf("want %v but got put: %v", ca.ok, ok)
		}
	}
}

func TestGet(t *testing.T) {
	buffer := NewRingBuffer[string](3)

	buffer.Put("one")
	buffer.Put("two")

	val, ok := buffer.Get()
	require.True(t, ok)
	require.Equal(t, val, "one")

	val, ok = buffer.Get()
	require.True(t, ok)
	require.Equal(t, val, "two")

	_, ok = buffer.Get()
	require.False(t, ok)

}