Skip to main content

Concurrency safety at a glance

All types in Go Container—Queue, Stack, OrderedMap, and Set—are not safe for concurrent use unless explicitly noted. Calling methods on these types from multiple goroutines without external synchronization will cause data races.The sole exception is WaitableQueue, which is designed for concurrent producer-consumer patterns and is fully concurrency-safe.

Adding external synchronization

For Queue, Stack, OrderedMap, and Set, wrap every access in a mutex.
Use sync.RWMutex if reads (e.g., Len() on a Countable) vastly outnumber writes and you need maximum read throughput. For the common case, a plain sync.Mutex is sufficient and easier to reason about. Alternatively, confine the collection to a single goroutine and communicate through channels—the standard Go concurrency pattern.

WaitableQueue

WaitableQueue is the purpose-built solution for concurrent producer-consumer pipelines. It is:
  • Unbounded: Enqueue never blocks, unlike a buffered channel that back-pressures the producer when full.
  • Signal-based: consumers wait on a channel returned by WaitChan() rather than spinning or polling.
  • Flow-aware: watermark states let producers throttle themselves before the queue grows without bound.

When to use WaitableQueue vs channels

Use WaitableQueue when:
  • You cannot afford to block the producer.
  • You want explicit, graduated flow control (warn at high watermark, shed load near saturation).
  • The queue must remain responsive under bursty load.
Use a plain channel when:
  • Back-pressure on the producer is acceptable or desirable.
  • You want the simplest possible implementation.

Watermark states

WaitableQueue reports one of five states after every Enqueue or Dequeue:
Do not use QueueIsBelowLowWatermark as the sole signal to resume a fully-stopped producer. If the producer has stopped calling Enqueue, it will never observe the state change and will never restart. Check watermark state on the consumer (Dequeue) side for reliable flow control, or poll periodically.

Constructor parameters

A saturation threshold above highWatermark is computed automatically from initialCapacity and highWatermark. You do not need to configure it directly.

Producer-consumer example

The pattern below shows a single producer and a single consumer. Scale to multiple goroutines by sharing the same WaitableQueue reference—it is safe to call Enqueue and Dequeue (and WaitChan) concurrently from any number of goroutines.

Closing the queue

Call q.Close() from the producer when no more items will be enqueued. This closes the signal channel returned by WaitChan(), permanently unblocking any consumer waiting on it. Consumers should drain remaining items after observing a closed channel.
Calling Enqueue on a closed WaitableQueue panics. Ensure only the producer calls Close, and only after it has finished enqueueing.

Using Len() safely

WaitableQueue implements container.Countable, so you can call Len() to inspect the current size. However, Len() acquires a separate mutex lock and its result is not atomic with respect to concurrent Enqueue or Dequeue calls.
Do not use Len() to make control-flow decisions (e.g., “dequeue only if Len() > 0”). By the time you act on the result, the queue may already be empty or have grown further. Use the WaitableQueueState returned by Enqueue and Dequeue for flow control, and reserve Len() for logging, metrics, and debugging.