2023-03-02 09:53:12 +00:00
|
|
|
// Package pq implements a concurrent priority queue.
|
|
|
|
//
|
|
|
|
// [Q] is similar to a buffered channel, except that senders attach to each
|
|
|
|
// item a priority, and receivers always get the highest-priority item.
|
|
|
|
//
|
|
|
|
// For example:
|
|
|
|
//
|
|
|
|
// import "gogs.humancabbage.net/sam/priorityq/pq"
|
|
|
|
// q := pq.Make[int, string](8)
|
|
|
|
// q.Send(1, "world")
|
|
|
|
// q.Send(2, "hello")
|
|
|
|
// _, word1, _ := pq.Recv()
|
|
|
|
// _, word2, _ := pq.Recv()
|
|
|
|
// fmt.Println(word1, word2)
|
2023-03-03 05:35:17 +00:00
|
|
|
// q.Close()
|
2023-03-02 09:53:12 +00:00
|
|
|
// // Output: hello world
|
|
|
|
//
|
|
|
|
// # Implementation
|
|
|
|
//
|
|
|
|
// Each queue has a [binary max-heap]. Sending and receiving items require
|
|
|
|
// heap-up and heap-down operations, respectively.
|
|
|
|
//
|
|
|
|
// [binary max-heap]: https://en.wikipedia.org/wiki/Binary_heap
|
|
|
|
package pq
|
|
|
|
|
|
|
|
import (
|
2024-08-22 07:53:48 +00:00
|
|
|
"iter"
|
2023-03-02 09:53:12 +00:00
|
|
|
"sync"
|
|
|
|
|
2023-03-03 05:35:17 +00:00
|
|
|
"gogs.humancabbage.net/sam/priorityq"
|
2023-03-02 09:53:12 +00:00
|
|
|
"gogs.humancabbage.net/sam/priorityq/binheap"
|
|
|
|
"golang.org/x/exp/constraints"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Q is a generic, concurrent priority queue.
|
|
|
|
type Q[P constraints.Ordered, T any] struct {
|
|
|
|
*state[P, T]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a new queue.
|
|
|
|
func Make[P constraints.Ordered, T any](cap int) Q[P, T] {
|
|
|
|
heap := binheap.Make[P, T](cap)
|
|
|
|
s := &state[P, T]{
|
|
|
|
heap: heap,
|
|
|
|
}
|
2023-03-03 05:35:17 +00:00
|
|
|
s.canRecv = sync.Cond{L: &s.mu}
|
|
|
|
s.canSend = sync.Cond{L: &s.mu}
|
2023-03-02 09:53:12 +00:00
|
|
|
return Q[P, T]{s}
|
|
|
|
}
|
|
|
|
|
|
|
|
type state[P constraints.Ordered, T any] struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
heap binheap.H[P, T]
|
2023-03-03 05:35:17 +00:00
|
|
|
canSend sync.Cond
|
|
|
|
canRecv sync.Cond
|
2023-03-02 09:53:12 +00:00
|
|
|
closed bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close marks the queue as closed.
|
|
|
|
//
|
|
|
|
// Attempting to close an already-closed queue results in a panic.
|
|
|
|
func (s *state[P, T]) Close() {
|
|
|
|
s.mu.Lock()
|
|
|
|
if s.closed {
|
2023-07-10 17:55:25 +00:00
|
|
|
s.mu.Unlock()
|
2023-03-02 09:53:12 +00:00
|
|
|
panic("close of closed queue")
|
|
|
|
}
|
|
|
|
s.closed = true
|
|
|
|
s.mu.Unlock()
|
|
|
|
s.canRecv.Broadcast()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recv gets an item, blocking when empty until one is available.
|
|
|
|
//
|
|
|
|
// This returns both the item itself and the its assigned priority.
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
func (s *state[P, T]) Recv() (P, T, bool) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
for {
|
|
|
|
for !s.closed && !s.heap.CanExtract() {
|
|
|
|
s.canRecv.Wait()
|
|
|
|
}
|
|
|
|
if s.closed && !s.heap.CanExtract() {
|
|
|
|
var emptyP P
|
|
|
|
var emptyT T
|
|
|
|
return emptyP, emptyT, false
|
|
|
|
}
|
|
|
|
if s.heap.CanExtract() {
|
|
|
|
priority, value := s.heap.Extract()
|
|
|
|
s.canSend.Broadcast()
|
|
|
|
return priority, value, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send adds an item with some priority, blocking if full.
|
|
|
|
func (s *state[P, T]) Send(priority P, value T) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
for {
|
|
|
|
for !s.closed && !s.heap.CanInsert() {
|
|
|
|
s.canSend.Wait()
|
|
|
|
}
|
|
|
|
if s.closed {
|
|
|
|
panic("send on closed queue")
|
|
|
|
}
|
|
|
|
if s.heap.CanInsert() {
|
|
|
|
s.heap.Insert(priority, value)
|
|
|
|
s.canRecv.Broadcast()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TryRecv attempts to get an item without blocking.
|
|
|
|
//
|
|
|
|
// This returns both the item itself and the its assigned priority.
|
|
|
|
//
|
2023-03-03 05:35:17 +00:00
|
|
|
// 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) {
|
2023-03-02 09:53:12 +00:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
if s.heap.CanExtract() {
|
|
|
|
priority, value = s.heap.Extract()
|
|
|
|
s.canSend.Broadcast()
|
|
|
|
return
|
|
|
|
}
|
2023-03-03 05:35:17 +00:00
|
|
|
if s.closed {
|
|
|
|
err = priorityq.ErrClosed
|
|
|
|
} else {
|
|
|
|
err = priorityq.ErrEmpty
|
|
|
|
}
|
2023-03-02 09:53:12 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TrySend attempts to add an item with some 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.
|
2023-03-03 05:35:17 +00:00
|
|
|
func (s *state[P, T]) TrySend(priority P, value T) error {
|
2023-03-02 09:53:12 +00:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
2023-03-03 05:35:17 +00:00
|
|
|
if s.closed {
|
|
|
|
return priorityq.ErrClosed
|
|
|
|
}
|
2023-03-02 09:53:12 +00:00
|
|
|
if !s.heap.CanInsert() {
|
2023-03-03 05:35:17 +00:00
|
|
|
return priorityq.ErrFull
|
2023-03-02 09:53:12 +00:00
|
|
|
}
|
|
|
|
s.heap.Insert(priority, value)
|
|
|
|
s.canRecv.Broadcast()
|
2023-03-03 05:35:17 +00:00
|
|
|
return nil
|
2023-03-02 09:53:12 +00:00
|
|
|
}
|
2024-08-22 07:53:48 +00:00
|
|
|
|
|
|
|
// Iter returns an iterator that consumes values until the queue is closed.
|
|
|
|
func (s *state[P, T]) Iter() iter.Seq[T] {
|
|
|
|
return func(yield func(T) bool) {
|
|
|
|
for {
|
|
|
|
_, t, ok := s.Recv()
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !yield(t) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|