Library / SDK
golang-queue/queue avatar
golang-queue/queue

golang-queue/queue: a goroutine pool with a pluggable queue backend

Queue is a Golang library for spawning and managing a Goroutine pool

631 stars50 forksGoMIT

At a glance

What is it?
This Go library gives you a bounded pool of goroutines and a queue in front of them, with the default backend a ring buffer in memory and adapters for NSQ, NATS, Redis Pub/Sub, Redis Streams and RabbitMQ behind the same interface. The interesting part is that the queue and the worker are separate objects, so the transport can change without the code that submits work changing.
Who is it for?
golang-queue/queue fits a Go service that has a bursty, CPU-bound workload and wants a bounded worker pool with a queue abstraction in front of it, especially one that may later need to move from an in-process ring buffer to a real message broker. It is the wrong choice if your work is IO-bound and already fits in Go's own goroutines, since a fixed pool of five workers is a limit rather than a feature in that case.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A pool of goroutines with a queue in front

The README describes Queue as a Golang library designed to help you create and manage a pool of Goroutines, lightweight threads, allowing you to run multiple tasks in parallel and use the full CPU capacity of the machine.

The framing is important. This is not a general purpose task runner, and it is not a work queue in the sense of a durable job system. It is a bounded pool of workers, with a queue abstraction, and the queue can be in memory or external.

The feature list shows the shape of it. Circular buffer queues are supported, which is the default backend for a simple queue service. Then there are integrations for real transports: NSQ for real-time distributed messaging, NATS for adaptive edge and distributed systems, Redis Pub/Sub, Redis Streams, and RabbitMQ. The queue scenario section describes a simple queue service using a ring buffer as the default backend, and then the same service switched to NSQ, NATS or Redis, with multiple producers and consumers supported in all cases.

So the promise is that the transport is an implementation detail. Your producers keep calling the same method whether the work sits in a ring buffer in the same process or in a broker on another machine.

Two ways to submit work, and they are not the same

The usage section documents two distinct entry points, and the difference between them is the main thing to understand before adopting the library.

The first is the task function route. By calling the `QueueTask()` method you schedule tasks to be executed by workers in the pool. The example creates the pool and immediately schedules a hundred jobs:

go
  taskN := 100
  rets := make(chan string, taskN)

  // initialize the queue pool
  q := queue.NewPool(5)
  // shut down the service and notify all workers
  // wait until all jobs are complete
  defer q.Release()

  // assign tasks to the queue

The second is the message queue route. There you define a new message struct and implement the `Bytes()` function to encode the message, and you use the `WithFn` function to handle messages from the queue. The message that arrives is a `core.TaskMessage`, and you decode the payload from it.

The difference in practice is that the task route hands the pool a function it will call, while the message route serialises your value, puts it through the queue backend, and calls a handler that decodes it. The second route is what makes an external broker possible, because it is the one that has a wire format at all.

Five workers, and Release is the shutdown contract

The pool is created with `queue.NewPool(5)`, so five workers, and the pool is released with a deferred `q.Release()`.

The comments around that call spell out the contract: shutting down the service notifies all workers and waits until all jobs are complete. That is the part of the API worth studying, because it is the difference between a pool you can drop into a service and one that loses work at shutdown.

The lifecycle in the examples is consistent. The pool is created, the deferred release is registered, tasks are assigned from a hundred goroutines, and the main goroutine then reads the results from a buffered channel with a small sleep between reads. The `QueueTask` call returns an error, and the first example panics on it while the message-queue example logs it. Both are reasonable, and the difference is instructive: the first treats a failure to enqueue as fatal, the second treats it as something to report.

Note what the examples do not hide. A pool of five workers against a hundred tasks means the tasks queue up, and the `time.Sleep` in the reading loop is there because the results arrive as the workers get to them. The pool bounds concurrency, and the bound is the point.

The backend is a worker you plug in

The external transports are handled by separate modules under the same organisation, `github.com/golang-queue/nsq` and `github.com/golang-queue/nats` for the two documented in detail, each with its own documentation. The README points at the NSQ documentation and the NATS documentation for more details.

The NSQ example defines the worker first, with the address, topic, channel and the concurrency limit:

go
  w := nsq.NewWorker(
    nsq.WithAddr("127.0.0.1:4150"),
    nsq.WithTopic("example"),
    nsq.WithChannel("foobar"),
    nsq.WithMaxInFlight(10),

and then passes that worker to the queue with `queue.WithWorker(w)`. The NATS example does the same with a different set of options, `WithAddr("127.0.0.1:4222")`, `WithSubj("example")` and `WithQueue("foobar")`, and it uses `queue.NewQueue` with `WithWorkerCount(10)` and an explicit `q.Start()` rather than the pool constructor.

That difference is worth noticing rather than glossing over. The pool API and the queue API are not the same call with different arguments, so moving from the in-process default to a broker is a change of constructor and of lifecycle, not just a change of option. The example code is the specification here, and the documentation for the per-backend modules is where the details live.

Race detection, goroutine leak checks and pinned tooling

The build tooling is short, and the test command is the interesting line:

code
GO ?= go
TOOLS_MOD := -modfile=go.tools.mod

## test: run tests
test:
	$(GO) test -race -v -cover -covermode=atomic -coverprofile=coverage.out ./...

The race detector is on by default for the test suite, which is the right call for a library whose entire purpose is running work concurrently. Coverage is written to a profile with atomic mode, and the rest of the Makefile covers dependency upgrades, downloading the golangci-lint tool dependency through a separate module file, and formatting and linting through that same tool.

The module file is equally telling. Beyond the usual testify and backoff, it requires `go.uber.org/goleak` and `go.uber.org/mock`, and it declares `go 1.26.8` while the README's Requirements section says Go version 1.22 or above.

The goroutine leak detector is the dependency to notice. A library that spawns workers has an obvious failure mode, workers that never exit, and goleak is how a test asserts that they did. The mocks package is used the same way, with a `mocks/` directory in the repository for faking the worker interface in tests.

The CI badges in the README are for CodeQL, the test workflow, codecov and a Trivy security scan, and the repository also carries a `.golangci.yml` and a `.goreleaser.yaml` for releases.

The stated Go requirement and the module file disagree

The Requirements section of the README says Go version 1.22 or above, and the module file declares:

code
module github.com/golang-queue/queue

go 1.26.8

Those are not the same statement, and the difference matters in practice. A go directive in the module file is a lower bound enforced by the toolchain: with go 1.26.8 in the module, building the package with an older toolchain is refused rather than warned about, depending on the toolchain's own rules.

So the practical floor for consuming this library is the module file's, and the README's 1.22 is either stale or is describing a different thing, such as the version at which the library's own features were written.

This is exactly the kind of detail to check before you commit, and it is a small example of why the module file is the better source for a Go dependency than the prose. The same applies to the release history: the recent versions are v0.5.0 from 2026-02-11, v0.4.0 from 2025-04-13 and v0.3.0 from 2025-01-20, and the repository is not archived with the last push on 2026-09-30.

A zero major version is the other thing to weigh. The API is not promised to be stable across a minor bump, so pinning an exact version in a go.mod is the sensible habit here.

The library is small, and the repository is larger than it needs to be

The top-level layout tells you the scope of the implementation. The core files are `pool.go`, `queue.go`, `ring.go`, `thread.go`, `options.go`, `recovery.go`, `logger.go`, `metric.go` and `errors.go`, with a `core/` directory for the shared interfaces, a `job/` directory, and a `mocks/` directory for the test doubles. The ring buffer, which is the default in-memory backend, is a self-contained file.

The test coverage is visible in the file names, with `pool_test.go`, `queue_test.go`, `ring_test.go`, `thread_test.go`, `options_test.go`, `logger_test.go` and `metric_test.go`, plus a `benchmark_test.go` and a `queue_example_test.go` that keeps the documentation honest.

There is also an `_example/` directory, and README translations into Traditional Chinese and Simplified Chinese, linked at the top of the README, which tells you something about where the users are. A `CLAUDE.md` sits at the root as well, alongside a `go.tools.mod` and `go.tools.sum` for the linting tool dependency, so the tool versions are pinned separately from the library's own dependencies.

The licence is MIT, with the LICENSE file at the root, and the release cadence of three versions across a year and a bit is consistent with a small library that is worked on in batches rather than continuously.

Editorial conclusion

golang-queue/queue fits a Go service that has a bursty, CPU-bound workload and wants a bounded worker pool with a queue abstraction in front of it, especially one that may later need to move from an in-process ring buffer to a real message broker. It is the wrong choice if your work is IO-bound and already fits in Go's own goroutines, since a fixed pool of five workers is a limit rather than a feature in that case. Verify first which Go version you are on, because the README and the module file disagree, and read the worker interface for your chosen backend before assuming the same delivery guarantees as the in-memory default.

Frequently asked questions

What does the golang-queue/queue library do?

It is a Go library for creating and managing a pool of goroutines so that multiple tasks run in parallel using the machine's CPU capacity. A queue sits in front of the workers, and the queue backend can be an in-memory ring buffer, NSQ, NATS, Redis Pub/Sub, Redis Streams or RabbitMQ.

How do I install golang-queue/queue?

With go get github.com/golang-queue/queue for the stable version, or go get github.com/golang-queue/queue@master for the latest. The README states a requirement of Go 1.22 or above, while the module file declares go 1.26.8, so the module file is the stricter of the two.

What is the difference between QueueTask and Queue in golang-queue/queue?

QueueTask schedules a function for a worker in the pool to execute. Queue takes a message that implements the Bytes method to encode it, sends it through the queue backend, and a handler set with WithFn decodes the payload from the core.TaskMessage.

How do I use NSQ or NATS as the queue backend?

You build a worker from the separate golang-queue/nsq or golang-queue/nats module, configure it with its own options such as WithAddr, WithTopic, WithChannel and WithMaxInFlight, and pass it to the queue with queue.WithWorker. The NSQ example uses queue.NewPool and the NATS example uses queue.NewQueue with WithWorkerCount and an explicit Start call.

How is golang-queue/queue tested?

The Makefile runs the suite with the race detector and coverage enabled, writing a coverage profile. The module also requires go.uber.org/goleak for goroutine leak detection and go.uber.org/mock, with a mocks directory in the repository, and the CI badges cover CodeQL, the test workflow, codecov and a Trivy security scan.

Official sources

  1. golang-queue/queue on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/golang-queue-queue.svg)](https://hysenlabs.com/projects/golang-queue-queue)

Was this useful?