Comparison vs other Go locking libraries

May 4, 2026 · View on GitHub

This page benchmarks ubgo/lock against the most-starred Go locking libraries on GitHub (as of 2026). The pitch isn't that any one ubgo backend is unique — it's that the family ships all five mechanisms behind one consistent interface, with crash safety, fencing, and observability across the board.

Field comparison

Columns are libraries; rows are features. ✅ = fully supported; 🟡 = partial / opt-in; ❌ = not supported.

Featureubgo/lock familygofrs/flockbsm/redislockgo-redsync/redsyncnikolaydubina/go-pglocketcd concurrency.Mutex
Marker-file backend✅ filelock
Kernel-fenced flock✅ flock
Redis backend✅ redislock✅ (Redlock)
Postgres backend✅ pglock
etcd backend✅ etcdlock
Shared interface across backendslock.Locker
In-memory test backend✅ memlock
Non-blocking acquire (ErrLocked)✅ everywhere🟡 (TryLock)🟡✅ TryLock
Context support✅ everywhere🟡
Crash recovery✅ each backend native✅ kernelTTLTTLsessionlease
Fencing tokens✅ filelock, redislock, etcdlock✅ mod_revision
Stale-marker sweep✅ filelockn/an/an/an/a
Semaphore (N holders)✅ filelock
TraceID propagation in markers✅ filelock
slog hooks✅ filelock
gocron adapter✅ contrib/gocronlock
Cross-platform (linux/macOS/win)✅ filelock, flock, memlock
Apache-2.0✅ Apache-2.0MITApache-2.0MITApache-2.0

When you'd pick each library

ScenarioPick
You only need flock(2) on a single hostgofrs/flock is fine; ubgo/lock/flock is identical scope with a Factory pattern.
Single Redis primary, one projectbsm/redislock is fine. ubgo/lock/redislock adds the shared interface.
Multi-master Redis with quorumgo-redsync/redsync (Redlock). ubgo/lock deliberately does NOT do Redlock.
Already running etcd clusterconcurrency.Mutex directly OR ubgo/lock/etcdlock for the Factory wrapper + family interface.
Need to swap backends across environmentsubgo/lock (the only family with a shared interface).
Want Postgres-advisory + Redis + flock + etc. without 4 dep trees in your serviceubgo/lock (each backend is its own Go module — only deps you import are pulled).
Want crash-recovery + fencing + observability out of the boxubgo/lock/filelock for single-host; the rest for distributed.
Want unit tests that don't touch infraubgo/lock/memlock is the drop-in.

Subtle correctness — what we got right

1. ErrLocked is the same sentinel everywhere

Every backend translates its native "already held" error into the same lock.ErrLocked (or its package-local <backend>.ErrLocked, which errors.Is against lock.ErrLocked via the AsLocker adapter). You write errors.Is(err, lock.ErrLocked) once and it works against any backend.

2. Lua-guarded Redis release

bsm/redislock does this; redsync/redsync does this; some others don't. We do — without the Lua guard, a delayed Release from a TTL-expired holder can DEL a successor's key, silently breaking the mutex.

3. Postgres session-tied means no TTL

pg_advisory_lock is held against the connection. We hold the connection for the lifetime of the lock; Holder.Release returns it to the pool. If the process dies, Postgres notices the session gone and releases. No TTL to tune — most other Postgres lock libraries get this right too.

4. etcd mod_revision as fencing token

etcd's concurrency.Mutex sets the lock key's mod_revision — which is monotonic across the whole cluster, not per-name. We expose it as Holder.Token() uint64. A stronger fence than per-name INCR.

5. Marker file with PID + start_time

PID alone is broken under PID reuse. We store pid_start from /proc/<pid>/stat (Linux) and compare on takeover. Other marker-file libraries (incl. the popular lace/filelock) skip this and have known stale-lock-after-PID-reuse bugs.

6. Operator-readable markers

cat /var/run/myservice/job.lock shows pid, host, acquired-at, strategy, stale_after, slot, and (when configured) the OTel TraceID. No other Go marker-file library exposes this much.

What's NOT comparable

We don't claim the family is the right tool for every locking problem. Things we deliberately don't do:

  • Reentrant locks (except pglock's native reentrancy)
  • Wait/block APIs (Acquire always returns immediately)
  • Redlock multi-master Redis
  • Distributed deadlock detection across multiple lock names (Redisson MultiLock territory)
  • Lock-server-as-a-service (HTTP-fronted lock coordinator; werf/lockgate's pattern)

If you need any of the above, this isn't the library. See non-goals.md for the reasoning.