Move priority queue to pq package; improve docs.

This commit is contained in:
2023-03-02 01:53:12 -08:00
parent 5e23a92314
commit b3b491d9a9
8 changed files with 255 additions and 155 deletions

View File

@@ -1,3 +1,30 @@
// Package mq implements a concurrent, dual-priority message queue.
//
// [Q] is similar to a buffered channel, except that senders can assign one of
// two priority levels to each item, "high" or "low." Receivers will always
// get a high-priority item ahead of any low-priority ones.
//
// For example:
//
// q := mq.Make[string](8)
// mq.SendLow("world")
// mq.SendHigh("hello")
// word1, _ := mq.Recv()
// word2, _ := mq.Recv()
// fmt.Println(word1, word2)
// pq.Close()
// // Output: hello world
//
// # Implementation
//
// Each queue has two circular buffers, one for each priority level.
// Currently, the capacities for these are fixed and equal. If one buffer is
// full, attempts to send further items with its priority level will block
// ([Q.Send]) or fail ([Q.TrySend]).
//
// Compared the pq package, the limitation on priority levels increases
// performance, as its circular buffers are much less expensive than the heap
// operations of a traditional priority queue.
package mq
import (
@@ -6,14 +33,7 @@ import (
"gogs.humancabbage.net/sam/priorityq/circ"
)
// Q is a precise, concurrent, prioritized message queue.
//
// Each queue has two internal buffers, high and low. This implementation
// guarantees that when there are items in both buffers, consumers receive
// ones from the high priority buffer first.
//
// Each buffer has the same capacity, set on initial construction. Sending to
// a buffer will block if it is full, even if the other buffer has space.
// Q is a concurrent, dual-priority message queue.
type Q[T any] struct {
*state[T]
}
@@ -44,16 +64,18 @@ type state[T any] struct {
// Close marks the queue as closed.
//
// Subsequent attempts to send will panic. Subsequent calls to Recv will
// continue to return the remaining items in the queue.
// Attempting to close an already-closed queue results in a panic.
func (s *state[T]) Close() {
s.mu.Lock()
if s.closed {
panic("close of closed queue")
}
s.closed = true
s.mu.Unlock()
s.canRecv.Broadcast()
}
// Recv returns an item from the prioritized buffers, blocking if empty.
// Recv gets an item, blocking when empty until one is available.
//
// The returned bool will be true if the queue still has items or is open.
// It will be false if the queue is empty and closed.
@@ -86,20 +108,19 @@ func (s *state[T]) Send(value T) {
s.SendLow(value)
}
// SendHigh adds an item to the high priority buffer, blocking if full.
// SendHigh adds an item with high priority, blocking if full.
func (s *state[T]) SendHigh(value T) {
s.send(value, &s.high, s.canSendHigh)
}
// SendLow adds an item to the low priority buffer, blocking if full.
// SendLow adds an item with low buffer, blocking if full.
func (s *state[T]) SendLow(value T) {
s.send(value, &s.low, s.canSendLow)
}
// TryRecv attempts to return an item from the prioritized buffers.
// TryRecv attempts to get an item from the queue, without blocking.
//
// This method does not block. If there is an item in a buffer, it returns
// true. If the buffer is empty, it returns false.
// If the attempt succeeds, the returned bool is true. Otherwise, it is false.
func (s *state[T]) TryRecv() (value T, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -118,18 +139,21 @@ func (s *state[T]) TryRecv() (value T, ok bool) {
return
}
// TrySendHigh attempts to add an item to the high priority buffer.
// TrySend is an alias for TrySendLow.
func (s *state[T]) TrySend(value T) bool {
return s.trySend(value, &s.low)
}
// TrySendHigh attempts to add an item with high priority, without blocking.
//
// This method does not block. If there is space in the buffer, it returns
// true. If the buffer is full, it returns false.
// If the attempt succeeds, the returned bool is true. Otherwise, it is false.
func (s *state[T]) TrySendHigh(value T) bool {
return s.trySend(value, &s.high)
}
// TrySendLow attempts to add an item to the low priority buffer.
// TrySendLow attempts to add an item with low priority, without blocking.
//
// This method does not block. If there is space in the buffer, it returns
// true. If the buffer is full, it returns false.
// If the attempt succeeds, the returned bool is true. Otherwise, it is false.
func (s *state[T]) TrySendLow(value T) bool {
return s.trySend(value, &s.low)
}

View File

@@ -62,6 +62,18 @@ func TestRecvClosed(t *testing.T) {
}
}
func TestDoubleClose(t *testing.T) {
t.Parallel()
q := mq.Make[int](4)
defer func() {
if r := recover(); r == nil {
t.Errorf("closing a closed queue did not panic")
}
}()
q.Close()
q.Close()
}
func TestTrySendRecv(t *testing.T) {
t.Parallel()
q := mq.Make[int](4)