go-utils is a collection of small, reusable Go utilities, with an emphasis on
bounded queues and concurrent object reuse. The module includes generic
single-threaded, mutex-protected, SPSC, and MPMC queues; a growable or
fixed-size slab pool; UUID and size helpers; build information formatting; and
a handful of synchronization and command-line helpers.
go get github.com/opencoff/go-utilsimport utils "github.com/opencoff/go-utils"The module requires Go 1.25 or newer. Its go.mod currently selects the Go
1.26 toolchain.
| API | Purpose |
|---|---|
Q[T] |
Bounded queue for single-threaded or externally synchronized use |
SyncQ[T] |
Mutex-protected bounded queue |
SPSCQ[T] |
Lock-free queue for exactly one producer and one consumer |
MPMCQ[T] |
Lock-free, linearizable multiple-producer/multiple-consumer queue |
Pool[T] |
Concurrent slab pool with lock-free reuse and serialized growth |
Bufpool |
Legacy channel-backed fixed-size blocking pool |
Barrier |
One-shot goroutine broadcast barrier |
UUID |
UUIDv4 generation and text/byte conversion |
ParseSize, HumanizeSize |
Parse and format binary byte sizes |
BuildInfo |
Human-readable and JSON build metadata |
Abbrev |
Generate unambiguous abbreviations from a word list |
Askpass |
Read and optionally verify a password from the terminal |
All queue operations are non-blocking: Enq returns false when full and
Deq returns false when empty. Requested capacities are rounded as needed
for the queue's power-of-two ring representation.
Choose the queue according to its concurrency contract:
| Queue | Producers | Consumers | Synchronization |
|---|---|---|---|
Q[T] |
One or externally synchronized | One or externally synchronized | None |
SyncQ[T] |
Multiple | Multiple | Mutex |
SPSCQ[T] |
Exactly one | Exactly one | Lock-free atomics |
MPMCQ[T] |
Multiple | Multiple | Lock-free SCQ rings |
q := utils.NewMPMCQ[string](1024)
if !q.Enq("work item") {
// The bounded queue is full.
}
item, ok := q.Deq()
if ok {
fmt.Println(item)
}MPMCQ uses two SCQ rings: one tracks occupied value slots and the other
tracks available slots. It is bounded, linearizable, and must not be copied
after first use. Len is only a momentary observation while producers and
consumers are active.
This is an implementation of the bounded SCQ algorithm published in the refereed proceedings of DISC 2019:
Ruslan Nikolaev, "A Scalable, Portable, and Memory-Efficient Lock-Free FIFO Queue", 33rd International Symposium on Distributed Computing (DISC 2019), LIPIcs 146, Article 28, pp. 28:1–28:16.
q := utils.NewSPSCQ[Packet](4096)
// Producer goroutine:
for !q.Enq(packet) {
runtime.Gosched()
}
// Consumer goroutine:
packet, ok := q.Deq()Only the designated producer may call Enq, and only the designated consumer
may call Deq. Use MPMCQ or SyncQ when either side has multiple
goroutines.
Call Flush only while a concurrent queue is quiescent.
Pool[T] retains objects in stable backing slabs. Returned objects are zeroed
before they enter their originating slab's freelist. Freelist entries are
integer offsets rather than pointers, reducing GC scanning and pointer write
barriers.
type Message struct {
Header []byte
Body []byte
}
pool := utils.NewPool[Message](1024, 2)
message := pool.Get()
message.Body = append(message.Body, payload...)
// Put clears message back to the zero value.
pool.Put(message)The first argument is the initial capacity. The second is the growth factor;
new slab sizes are rounded to powers of two. Growth is serialized by a mutex,
while ordinary Get and Put reuse paths remain lock-free.
If the growth factor is less than one, the pool is fixed-size and Get
returns nil when all objects are checked out:
pool := utils.NewPool[Message](128, 0.5)
message := pool.Get()
if message == nil {
// Fixed pool is exhausted.
return
}
defer pool.Put(message)Every non-nil object returned by Get must be passed exactly once to the same
pool. Do not pass copied, interior, foreign, or already returned pointers to
Put. A Pool must not be copied after first use. Zero-sized element types
are not supported.
| Type | Exhaustion behavior | Retention | Object reset |
|---|---|---|---|
Pool[T], growth >= 1 |
Allocates another slab | Stable until pool is unreachable | Zeroes on Put |
Pool[T], growth < 1 |
Get returns nil |
Stable until pool is unreachable | Zeroes on Put |
sync.Pool |
May allocate through New |
Entries may disappear at any GC | Caller-managed |
Bufpool |
Get blocks |
Fixed set of objects | Caller-managed |
sync.Pool is substantially faster under high contention because it uses
per-P local storage, but it is a transient cache rather than a retained,
bounded allocator.
ParseSize accepts binary suffixes such as k, M, G, and their B
forms:
size, err := utils.ParseSize("32MB") // 32 * 1024 * 1024
if err != nil {
return err
}
fmt.Println(utils.HumanizeSize(size)) // 32MBThe MiB spelling is not accepted; use M or MB.
UUID helpers generate RFC 4122 version 4 identifiers and support compact hex and conventional hyphenated forms:
id := utils.NewUUID()
fmt.Println(id.String())
encoded := id.Marshal()
decoded := utils.UnmarshalUUID(encoded)MakeUUID constructs a UUID from exactly 16 bytes.
ReadBuildInfo wraps runtime/debug.ReadBuildInfo and exposes readable and
JSON representations:
if info, ok := utils.ReadBuildInfo(); ok {
fmt.Print(info.String())
data, err := info.JSON()
if err != nil {
return err
}
fmt.Println(data)
}Abbrevmaps unique word prefixes back to their full words.Barrierreleases all current and future waiters after oneBroadcast.Askpassreads a password from standard input without terminal echo and can ask for confirmation.Bufpoolcreates a fixed set of interface-valued objects;Getblocks when empty and an invalid extraPutpanics.
# Unit and concurrency tests
go test ./...
# Race detector and coverage
go test -race -count=1 -covermode=atomic -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out
# Queue and pool benchmarks
go test -run '^$' -bench 'Benchmark(SPSCQ|MPMCQ|Pool)$' -benchmem .GitHub Actions runs the race-enabled test suite and enforces at least 90%
statement coverage. The BenchmarkPool cases compare the slab allocator with
sync.Pool in serial and parallel workloads.
The complete exported API and type documentation are available on pkg.go.dev.
Licensing notices are provided in the individual source files.