Various improvements.

* Expand README.md, provide benchmark results.
* Add docs, benchmarks for binheap and circ packages.
* Add methods Len() and Capacity().
* Change *sync.Cond to sync.Cond.
* TryRecv() and TrySend() distinguish empty and closed errors.
* Improve test coverage.
* Add basic Makefile.
* Fix documentation mistakes.
This commit is contained in:
2023-03-02 21:35:17 -08:00
parent b3b491d9a9
commit b00fe25128
12 changed files with 313 additions and 104 deletions

View File

@@ -12,7 +12,7 @@
// _, word1, _ := pq.Recv()
// _, word2, _ := pq.Recv()
// fmt.Println(word1, word2)
// pq.Close()
// q.Close()
// // Output: hello world
//
// # Implementation
@@ -26,6 +26,7 @@ package pq
import (
"sync"
"gogs.humancabbage.net/sam/priorityq"
"gogs.humancabbage.net/sam/priorityq/binheap"
"golang.org/x/exp/constraints"
)
@@ -41,16 +42,16 @@ func Make[P constraints.Ordered, T any](cap int) Q[P, T] {
s := &state[P, T]{
heap: heap,
}
s.canRecv = sync.NewCond(&s.mu)
s.canSend = sync.NewCond(&s.mu)
s.canRecv = sync.Cond{L: &s.mu}
s.canSend = sync.Cond{L: &s.mu}
return Q[P, T]{s}
}
type state[P constraints.Ordered, T any] struct {
mu sync.Mutex
heap binheap.H[P, T]
canSend *sync.Cond
canRecv *sync.Cond
canSend sync.Cond
canRecv sync.Cond
closed bool
}
@@ -116,16 +117,21 @@ func (s *state[P, T]) Send(priority P, value T) {
//
// This returns both the item itself and the its assigned priority.
//
// If the attempt succeeds, the returned bool is true. Otherwise, it is false.
func (s *state[P, T]) TryRecv() (priority P, value T, ok bool) {
// The error indicates whether the attempt succeeded, the queue is empty, or
// the queue is closed.
func (s *state[P, T]) TryRecv() (priority P, value T, err error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.heap.CanExtract() {
priority, value = s.heap.Extract()
ok = true
s.canSend.Broadcast()
return
}
if s.closed {
err = priorityq.ErrClosed
} else {
err = priorityq.ErrEmpty
}
return
}
@@ -133,13 +139,16 @@ func (s *state[P, T]) TryRecv() (priority P, value T, ok bool) {
//
// This method does not block. If there is space in the buffer, it returns
// true. If the buffer is full, it returns false.
func (s *state[P, T]) TrySend(priority P, value T) bool {
func (s *state[P, T]) TrySend(priority P, value T) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return priorityq.ErrClosed
}
if !s.heap.CanInsert() {
return false
return priorityq.ErrFull
}
s.heap.Insert(priority, value)
s.canRecv.Broadcast()
return true
return nil
}

View File

@@ -6,6 +6,7 @@ import (
"sync"
"testing"
"gogs.humancabbage.net/sam/priorityq"
"gogs.humancabbage.net/sam/priorityq/pq"
)
@@ -78,14 +79,14 @@ func TestTrySendRecv(t *testing.T) {
t.Parallel()
q := pq.Make[int, int](4)
assumeSendOk := func(n int) {
ok := q.TrySend(n, n)
if !ok {
err := q.TrySend(n, n)
if err != nil {
t.Errorf("expected to be able to send")
}
}
assumeRecvOk := func(expected int) {
_, actual, ok := q.TryRecv()
if !ok {
_, actual, err := q.TryRecv()
if err != nil {
t.Errorf("expected to be able to receive")
}
if actual != expected {
@@ -96,8 +97,8 @@ func TestTrySendRecv(t *testing.T) {
assumeSendOk(2)
assumeSendOk(3)
assumeSendOk(4)
ok := q.TrySend(5, 5)
if ok {
err := q.TrySend(5, 5)
if err == nil {
t.Errorf("expected queue to be full")
}
assumeRecvOk(4)
@@ -105,10 +106,19 @@ func TestTrySendRecv(t *testing.T) {
assumeRecvOk(2)
assumeRecvOk(1)
_, _, ok = q.TryRecv()
if ok {
_, _, err = q.TryRecv()
if err != priorityq.ErrEmpty {
t.Errorf("expected queue to be empty")
}
q.Close()
_, _, err = q.TryRecv()
if err != priorityq.ErrClosed {
t.Errorf("expected queue to be closed ")
}
err = q.TrySend(1, 1)
if err != priorityq.ErrClosed {
t.Errorf("expected queue to be closed ")
}
}
func TestConcProducerConsumer(t *testing.T) {