Rename package circ to queue.

This commit is contained in:
Sam Fredrickson 2023-03-03 20:09:51 -08:00
parent b00fe25128
commit f7474fb673
4 changed files with 109 additions and 104 deletions

View File

@ -1,70 +0,0 @@
// Package circ implements a circular FIFO buffer.
package circ
// B is a circular FIFO buffer.
type B[T any] struct {
buf []T
len int
head int
tail int
}
// Make creates a new buffer.
func Make[T any](cap int) B[T] {
buf := make([]T, cap)
return B[T]{buf: buf}
}
// Capacity returns the total capacity of the buffer.
func (b *B[T]) Capacity() int {
return cap(b.buf)
}
// Len returns the number of items in the buffer.
func (b *B[T]) Len() int {
return b.len
}
// CanPush returns true if the buffer has space for new items.
func (b *B[T]) CanPush() bool {
return cap(b.buf)-b.len != 0
}
// CanPop returns true if the buffer has one or more items.
func (b *B[T]) CanPop() bool {
return b.len != 0
}
// PopFront returns the front-most item from the buffer.
//
// If the buffer is empty, it panics.
func (b *B[T]) PopFront() T {
if !b.CanPop() {
panic("cannot pop from empty buffer")
}
item := b.buf[b.head]
// clear buffer slot so as not to hold on to garbage
var empty T
b.buf[b.head] = empty
b.len--
b.head++
if b.head == cap(b.buf) {
b.head = 0
}
return item
}
// PushBack adds an item to the end of the buffer.
//
// If the buffer is full, it panics.
func (b *B[T]) PushBack(value T) {
if !b.CanPush() {
panic("cannot push back to full buffer")
}
b.buf[b.tail] = value
b.len++
b.tail++
if b.tail == cap(b.buf) {
b.tail = 0
}
}

View File

@ -31,7 +31,7 @@ import (
"sync" "sync"
"gogs.humancabbage.net/sam/priorityq" "gogs.humancabbage.net/sam/priorityq"
"gogs.humancabbage.net/sam/priorityq/circ" "gogs.humancabbage.net/sam/priorityq/queue"
) )
// Q is a concurrent, dual-priority message queue. // Q is a concurrent, dual-priority message queue.
@ -41,8 +41,8 @@ type Q[T any] struct {
// Make a new queue. // Make a new queue.
func Make[T any](cap int) Q[T] { func Make[T any](cap int) Q[T] {
high := circ.Make[T](cap) high := queue.Make[T](cap)
low := circ.Make[T](cap) low := queue.Make[T](cap)
s := &state[T]{ s := &state[T]{
high: high, high: high,
low: low, low: low,
@ -55,8 +55,8 @@ func Make[T any](cap int) Q[T] {
type state[T any] struct { type state[T any] struct {
mu sync.Mutex mu sync.Mutex
high circ.B[T] high queue.Q[T]
low circ.B[T] low queue.Q[T]
canSendHigh sync.Cond canSendHigh sync.Cond
canSendLow sync.Cond canSendLow sync.Cond
canRecv sync.Cond canRecv sync.Cond
@ -163,7 +163,7 @@ func (s *state[T]) TrySendLow(value T) error {
return s.trySend(value, &s.low) return s.trySend(value, &s.low)
} }
func (s *state[T]) send(value T, buf *circ.B[T], cond *sync.Cond) { func (s *state[T]) send(value T, buf *queue.Q[T], cond *sync.Cond) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
for { for {
@ -181,7 +181,7 @@ func (s *state[T]) send(value T, buf *circ.B[T], cond *sync.Cond) {
} }
} }
func (s *state[T]) trySend(value T, buf *circ.B[T]) error { func (s *state[T]) trySend(value T, buf *queue.Q[T]) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.closed { if s.closed {

75
queue/lib.go Normal file
View File

@ -0,0 +1,75 @@
// Package q implements a non-concurrent queue.
//
// # Implementation
//
// [Q] is a classic ring buffer. It tracks its head, tail, and length. This
// makes determining whether the queue is full or empty trivial.
package queue
// Q is a non-concurrent queue.
type Q[T any] struct {
buf []T
len int
head int
tail int
}
// Make creates a new queue.
func Make[T any](cap int) Q[T] {
buf := make([]T, cap)
return Q[T]{buf: buf}
}
// Capacity returns the total capacity of the queue.
func (b *Q[T]) Capacity() int {
return cap(b.buf)
}
// Len returns the number of items in the queue.
func (b *Q[T]) Len() int {
return b.len
}
// CanPush returns true if the queue has space for new items.
func (b *Q[T]) CanPush() bool {
return cap(b.buf)-b.len != 0
}
// CanPop returns true if the queue has one or more items.
func (b *Q[T]) CanPop() bool {
return b.len != 0
}
// PopFront returns the front-most item from the queue.
//
// If the queue is empty, it panics.
func (b *Q[T]) PopFront() T {
if !b.CanPop() {
panic("cannot pop from empty queue")
}
item := b.buf[b.head]
// clear queue slot so as not to hold on to garbage
var empty T
b.buf[b.head] = empty
b.len--
b.head++
if b.head == cap(b.buf) {
b.head = 0
}
return item
}
// PushBack adds an item to the end of the queue.
//
// If the queue is full, it panics.
func (b *Q[T]) PushBack(value T) {
if !b.CanPush() {
panic("cannot push back to full queue")
}
b.buf[b.tail] = value
b.len++
b.tail++
if b.tail == cap(b.buf) {
b.tail = 0
}
}

View File

@ -1,28 +1,28 @@
package circ_test package queue_test
import ( import (
"math/rand" "math/rand"
"testing" "testing"
"gogs.humancabbage.net/sam/priorityq/circ" "gogs.humancabbage.net/sam/priorityq/queue"
) )
func TestRepeatPushPop(t *testing.T) { func TestRepeatPushPop(t *testing.T) {
t.Parallel() t.Parallel()
cb := circ.Make[int](4) q := queue.Make[int](4)
if cb.Capacity() != 4 { if q.Capacity() != 4 {
t.Errorf("wrong capacity") t.Errorf("wrong capacity")
} }
for i := 0; i < 50; i++ { for i := 0; i < 50; i++ {
cb.PushBack(1) q.PushBack(1)
cb.PushBack(2) q.PushBack(2)
cb.PushBack(3) q.PushBack(3)
cb.PushBack(4) q.PushBack(4)
if cb.Len() != 4 { if q.Len() != 4 {
t.Errorf("wrong length") t.Errorf("wrong length")
} }
checkPop := func(n int) { checkPop := func(n int) {
if v := cb.PopFront(); v != n { if v := q.PopFront(); v != n {
t.Errorf("popped %d, expected %d", v, n) t.Errorf("popped %d, expected %d", v, n)
} }
} }
@ -35,18 +35,18 @@ func TestRepeatPushPop(t *testing.T) {
func TestInterleavedPushPop(t *testing.T) { func TestInterleavedPushPop(t *testing.T) {
t.Parallel() t.Parallel()
cb := circ.Make[int](4) q := queue.Make[int](4)
checkPop := func(n int) { checkPop := func(n int) {
if v := cb.PopFront(); v != n { if v := q.PopFront(); v != n {
t.Errorf("popped %d, expected %d", v, n) t.Errorf("popped %d, expected %d", v, n)
} }
} }
cb.PushBack(1) q.PushBack(1)
cb.PushBack(2) q.PushBack(2)
cb.PushBack(3) q.PushBack(3)
checkPop(1) checkPop(1)
cb.PushBack(4) q.PushBack(4)
cb.PushBack(5) q.PushBack(5)
checkPop(2) checkPop(2)
} }
@ -57,8 +57,8 @@ func TestEmptyPopPanic(t *testing.T) {
} }
}() }()
t.Parallel() t.Parallel()
cb := circ.Make[int](4) q := queue.Make[int](4)
cb.PopFront() q.PopFront()
} }
func TestFullPushPanic(t *testing.T) { func TestFullPushPanic(t *testing.T) {
@ -68,13 +68,13 @@ func TestFullPushPanic(t *testing.T) {
} }
}() }()
t.Parallel() t.Parallel()
cb := circ.Make[int](1) q := queue.Make[int](1)
cb.PushBack(1) q.PushBack(1)
cb.PushBack(2) q.PushBack(2)
} }
func BenchmarkPush(b *testing.B) { func BenchmarkPush(b *testing.B) {
cb := circ.Make[int](b.N) q := queue.Make[int](b.N)
rs := rand.NewSource(0) rs := rand.NewSource(0)
r := rand.New(rs) r := rand.New(rs)
items := make([]int, b.N) items := make([]int, b.N)
@ -83,19 +83,19 @@ func BenchmarkPush(b *testing.B) {
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
cb.PushBack(items[i]) q.PushBack(items[i])
} }
} }
func BenchmarkPop(b *testing.B) { func BenchmarkPop(b *testing.B) {
cb := circ.Make[int](b.N) q := queue.Make[int](b.N)
rs := rand.NewSource(0) rs := rand.NewSource(0)
r := rand.New(rs) r := rand.New(rs)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
cb.PushBack(r.Int()) q.PushBack(r.Int())
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
cb.PopFront() q.PopFront()
} }
} }