concurrent-map

A thread-safe, sharded map for Go that avoids the lock contention of a single global mutex.

Library
Go
vv1.0.0
4,528stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
49/100Fair
Development Activity0
Maintenance20
Community76
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
65/100Good
Architecture78
Code Quality68
Innovation70
Learning Curve45

concurrent-map provides a thread-safe map type for Go, filling the gap left by the language’s built-in map, which does not support concurrent reads and writes. Rather than guarding the whole map with one mutex, it shards keys across a configurable number of independently-locked sub-maps (32 by default), so goroutines touching different shards don’t block each other.

The library predates Go’s stdlib sync.Map (added in Go 1.9) and remains a common choice for workloads that aren’t append-only, such as in-memory caches and counters, where sync.Map is a poor fit. It exposes a generics-based API (Go 1.18+) with Set, Get, Upsert, Remove, and buffered/callback-based iteration, plus JSON marshal/unmarshal support so a ConcurrentMap can be serialized like a plain map.

What You Get

  • A generic ConcurrentMap[K, V] type usable as a drop-in thread-safe alternative to Go’s built-in map
  • Sharded internal storage (32 shards by default) with per-shard sync.RWMutex locking to reduce contention
  • Atomic Upsert and conditional RemoveCb operations for safe read-modify-write patterns under lock
  • Buffered and callback-based iteration (IterBuffered, IterCb, Keys, Items) that snapshots shards without holding all locks at once
  • Built-in MarshalJSON/UnmarshalJSON so a concurrent map serializes and deserializes like a regular map

Common Use Cases

  • In-memory caches shared across many goroutines in a web server or worker pool
  • Concurrent counters or aggregation tables updated by parallel pipelines
  • Session or connection registries in network servers where entries are frequently added and removed (not append-only)
  • Any hot path currently wrapping a plain map in a single sync.Mutex that has become a contention bottleneck

Under The Hood

Architecture The entire package is a single file (concurrent_map.go) built around one exported type, ConcurrentMap[K, V], which holds a slice of ConcurrentMapShared[K, V] shards, each an unexported map plus its own sync.RWMutex. GetShard hashes a key via FNV-32 (fnv32/strfnv32) to pick a shard index, so every read (Get, Has, Count) takes an RLock on just one shard and every write (Set, Upsert, Remove, Pop) takes a Lock on just one shard, instead of contending on a single map-wide lock. Iteration is the one place that needs a full-map view: snapshot() fans out a goroutine per shard to RLock it, drain its items into a buffered channel, and fanIn merges those channels into one, so a full scan never holds every shard lock simultaneously. It’s a flat, single-abstraction design with no layering or dependency injection — changing the shard count or hash function touches every consumer of GetShard at once, since all locking and iteration logic is keyed off that index.

Tech Stack Pure Go standard library only — sync for the per-shard RWMutexes and WaitGroups, encoding/json for marshal/unmarshal, and fmt for the Stringer constraint used by NewStringer. The module (go.mod) targets Go 1.18, the version that introduced generics, which the API relies on throughout (ConcurrentMap[K comparable, V any]). There are no runtime dependencies, no build tooling beyond go build/go test, and CI is configured through a legacy Travis CI file (.travis.yml) rather than GitHub Actions.

Code Quality The repo carries both a substantial test file (concurrent_map_test.go) exercising creation, insertion, upsert, removal, iteration, and JSON round-tripping, and a dedicated benchmark file (concurrent_map_bench_test.go) comparing sharded-map performance against naive locking approaches. Error handling is idiomatic Go rather than exception-based: lookups return a (value, bool) pair, and only MarshalJSON/UnmarshalJSON return a standard error. Naming follows conventional Go style (exported PascalCase API, unexported camelCase helpers), and type safety comes from the comparable/any generic constraints rather than runtime type assertions. No dedicated linter configuration is present in the repo; formatting relies on gofmt conventions.

What Makes It Unique The public API is deliberately shaped to mirror Go’s built-in map (Set/Get/Remove/Has/Count), keeping the learning curve close to zero for anyone already using native maps, while adding concurrency-specific primitives — Upsert for atomic read-modify-write under a callback, and RemoveCb for conditional deletion under lock — that the built-in map and sync.Map don’t offer. Multiple generic constructors (New, NewStringer, NewWithCustomShardingFunction) let callers opt into custom key types and sharding strategies without subclassing, and transparent JSON support means a concurrent map can be dropped into existing serialization code with no extra glue.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search