The syncmap package provides a concurrent map implementation. It is a type alias to sync.Map from the Go standard library, which was originally prototyped in this package before being added to the standard library in Go 1.9.
import "golang.org/x/sync/syncmap"Or use the standard library directly:
import "sync"
// Then use sync.Map instead of syncmap.MapThe Map type is designed for concurrent use by multiple goroutines without additional locking. It's optimized for two common scenarios:
For other scenarios, a regular map with separate sync.RWMutex may have better performance.
type Map = sync.MapMap is a concurrent map with amortized-constant-time loads, stores, and deletes. It is safe for multiple goroutines to call a Map's methods concurrently.
Zero value:
Important:
Since Map is a type alias to sync.Map, it inherits all methods from the standard library. Here are the available operations:
func (m *Map) Load(key any) (value any, ok bool)Loads the value stored in the map for a key, or nil if no value is present.
Parameters:
key: The key to look up (any comparable type)Returns:
value: The value stored for the key, or nilok: true if the key was present, false otherwiseExample:
var m syncmap.Map
m.Store("key", "value")
value, ok := m.Load("key")
if ok {
fmt.Println(value.(string)) // "value"
}func (m *Map) Store(key, value any)Stores a value for a key.
Parameters:
key: The key to store (any comparable type)value: The value to store (any type)Example:
var m syncmap.Map
m.Store("user:123", &User{ID: 123, Name: "Alice"})func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)Loads a value if present, otherwise stores the provided value.
Parameters:
key: The key to look up or storevalue: The value to store if key is not presentReturns:
actual: The value now stored for the key (either existing or newly stored)loaded: true if the value was loaded (existed), false if it was stored (new)Use cases:
Example:
var m syncmap.Map
// First call: stores and returns the value
actual, loaded := m.LoadOrStore("key", "value1")
fmt.Println(actual, loaded) // "value1" false
// Second call: returns existing value
actual, loaded = m.LoadOrStore("key", "value2")
fmt.Println(actual, loaded) // "value1" truefunc (m *Map) LoadAndDelete(key any) (value any, loaded bool)Loads a value and deletes it atomically.
Parameters:
key: The key to load and deleteReturns:
value: The value that was stored, or nilloaded: true if the key was present, false otherwiseExample:
var m syncmap.Map
m.Store("key", "value")
value, loaded := m.LoadAndDelete("key")
fmt.Println(value, loaded) // "value" true
// Key is now deleted
value, loaded = m.Load("key")
fmt.Println(value, loaded) // nil falsefunc (m *Map) Delete(key any)Deletes the value for a key.
Parameters:
key: The key to deleteBehavior:
Example:
var m syncmap.Map
m.Store("key", "value")
m.Delete("key")func (m *Map) Range(f func(key, value any) bool)Calls f sequentially for each key and value present in the map.
Parameters:
f: Function called for each entry. Return false to stop iteration.Behavior:
Example:
var m syncmap.Map
m.Store("key1", "value1")
m.Store("key2", "value2")
m.Range(func(key, value any) bool {
fmt.Printf("%s: %s\n", key, value)
return true // Continue iteration
})func (m *Map) CompareAndSwap(key, old, new any) boolSwaps the value for a key if the current value equals old.
Parameters:
key: The key to updateold: The expected current valuenew: The new value to storeReturns:
bool: true if the swap occurred, false otherwiseUse cases:
Example:
var m syncmap.Map
m.Store("counter", 0)
// Try to update from 0 to 1
swapped := m.CompareAndSwap("counter", 0, 1)
fmt.Println(swapped) // true
// Try to update from 0 to 2 (fails because value is now 1)
swapped = m.CompareAndSwap("counter", 0, 2)
fmt.Println(swapped) // falsefunc (m *Map) CompareAndDelete(key, old any) boolDeletes the entry for a key if the current value equals old.
Parameters:
key: The key to deleteold: The expected current valueReturns:
bool: true if the deletion occurred, false otherwiseExample:
var m syncmap.Map
m.Store("key", "value")
// Try to delete if value is "value"
deleted := m.CompareAndDelete("key", "value")
fmt.Println(deleted) // true
// Key is now gone
value, ok := m.Load("key")
fmt.Println(ok) // falsefunc (m *Map) Swap(key, value any) (previous any, loaded bool)Swaps the value for a key and returns the previous value.
Parameters:
key: The key to swapvalue: The new value to storeReturns:
previous: The previous value, or nil if key didn't existloaded: true if the key existed, false if this is a new entryExample:
var m syncmap.Map
m.Store("key", "value1")
previous, loaded := m.Swap("key", "value2")
fmt.Println(previous, loaded) // "value1" true
value, _ := m.Load("key")
fmt.Println(value) // "value2"package main
import (
"fmt"
"golang.org/x/sync/syncmap"
"sync"
)
func main() {
var m syncmap.Map
var wg sync.WaitGroup
// Multiple goroutines writing
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
m.Store(id, fmt.Sprintf("value-%d", id))
}(i)
}
// Multiple goroutines reading
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
if value, ok := m.Load(id); ok {
fmt.Printf("Loaded: %s\n", value)
}
}(i)
}
wg.Wait()
}package main
import (
"fmt"
"golang.org/x/sync/syncmap"
"time"
)
type Cache struct {
data syncmap.Map
}
func (c *Cache) Get(key string) (string, bool) {
value, ok := c.data.Load(key)
if !ok {
return "", false
}
return value.(string), true
}
func (c *Cache) Set(key, value string) {
c.data.Store(key, value)
}
func (c *Cache) GetOrSet(key string, fn func() string) string {
// Try to load first
if value, ok := c.Get(key); ok {
return value
}
// Compute value
value := fn()
// Store and return (might store duplicate if concurrent, but that's ok)
actual, _ := c.data.LoadOrStore(key, value)
return actual.(string)
}
func main() {
cache := &Cache{}
// Concurrent access
for i := 0; i < 100; i++ {
go func(id int) {
key := fmt.Sprintf("key-%d", id%10)
value := cache.GetOrSet(key, func() string {
time.Sleep(10 * time.Millisecond)
return fmt.Sprintf("computed-%s", key)
})
fmt.Println(value)
}(i)
}
time.Sleep(time.Second)
}package main
import (
"fmt"
"golang.org/x/sync/syncmap"
"sync"
)
type Counter struct {
m syncmap.Map
}
func (c *Counter) Increment(key string) int {
for {
value, ok := c.m.Load(key)
if !ok {
// Try to initialize to 1
if _, loaded := c.m.LoadOrStore(key, 1); !loaded {
return 1
}
continue
}
current := value.(int)
next := current + 1
if c.m.CompareAndSwap(key, current, next) {
return next
}
// CAS failed, retry
}
}
func (c *Counter) Get(key string) int {
value, ok := c.m.Load(key)
if !ok {
return 0
}
return value.(int)
}
func main() {
counter := &Counter{}
var wg sync.WaitGroup
// 100 goroutines incrementing same counter
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment("requests")
}()
}
wg.Wait()
fmt.Printf("Final count: %d\n", counter.Get("requests")) // 100
}package main
import (
"fmt"
"golang.org/x/sync/syncmap"
)
type ServiceRegistry struct {
services syncmap.Map
}
type Service interface {
Start() error
Stop() error
}
func (r *ServiceRegistry) Register(name string, service Service) error {
_, loaded := r.services.LoadOrStore(name, service)
if loaded {
return fmt.Errorf("service %s already registered", name)
}
return nil
}
func (r *ServiceRegistry) Get(name string) (Service, bool) {
value, ok := r.services.Load(name)
if !ok {
return nil, false
}
return value.(Service), true
}
func (r *ServiceRegistry) Unregister(name string) bool {
_, loaded := r.services.LoadAndDelete(name)
return loaded
}
func (r *ServiceRegistry) All() []Service {
var services []Service
r.services.Range(func(key, value any) bool {
services = append(services, value.(Service))
return true
})
return services
}package main
import (
"fmt"
"golang.org/x/sync/syncmap"
)
type EventBus struct {
handlers syncmap.Map // map[string][]func(interface{})
}
func (e *EventBus) Subscribe(event string, handler func(interface{})) {
for {
value, ok := e.handlers.Load(event)
if !ok {
// Initialize handler slice
handlers := []func(interface{}){handler}
if _, loaded := e.handlers.LoadOrStore(event, handlers); !loaded {
return
}
continue
}
handlers := value.([]func(interface{}))
newHandlers := append(handlers, handler)
if e.handlers.CompareAndSwap(event, handlers, newHandlers) {
return
}
// CAS failed, retry
}
}
func (e *EventBus) Publish(event string, data interface{}) {
value, ok := e.handlers.Load(event)
if !ok {
return
}
handlers := value.([]func(interface{}))
for _, handler := range handlers {
go handler(data)
}
}
func main() {
bus := &EventBus{}
bus.Subscribe("user.created", func(data interface{}) {
fmt.Printf("Handler 1: %v\n", data)
})
bus.Subscribe("user.created", func(data interface{}) {
fmt.Printf("Handler 2: %v\n", data)
})
bus.Publish("user.created", map[string]string{"id": "123", "name": "Alice"})
}package main
import (
"fmt"
"golang.org/x/sync/syncmap"
)
func main() {
var m syncmap.Map
// Populate map
for i := 0; i < 100; i++ {
m.Store(i, i*i)
}
// Find first value > 100
found := false
var result int
m.Range(func(key, value any) bool {
v := value.(int)
if v > 100 {
result = v
found = true
return false // Stop iteration
}
return true // Continue
})
if found {
fmt.Printf("Found: %d\n", result)
}
}sync.Map (and syncmap.Map) is optimized for specific use cases:
For general-purpose concurrent map access, especially with frequent writes to the same keys, a regular map with sync.RWMutex may perform better:
type SyncMap struct {
mu sync.RWMutex
m map[string]interface{}
}
func (s *SyncMap) Load(key string) (interface{}, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
v, ok := s.m[key]
return v, ok
}
func (s *SyncMap) Store(key string, value interface{}) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key] = value
}Use type assertions carefully: All values are any, always check types:
value, ok := m.Load(key)
if !ok {
return ErrNotFound
}
str, ok := value.(string)
if !ok {
return ErrWrongType
}Don't copy Map: Maps must not be copied after first use. Always use pointers:
// Wrong
func process(m syncmap.Map) { }
// Correct
func process(m *syncmap.Map) { }Consider LoadOrStore for initialization: Avoid race conditions during initialization:
// Race condition
if _, ok := m.Load(key); !ok {
m.Store(key, initialize()) // Multiple goroutines might initialize
}
// Atomic
m.LoadOrStore(key, initialize())Use Range sparingly: Range iterates over all entries, which can be expensive for large maps. Consider whether you need a different data structure if you frequently iterate.
Be aware of Range consistency: Entries added/removed during Range may or may not be visited. Don't rely on seeing a consistent snapshot.
Use CompareAndSwap for atomic updates: When updating values based on their current state:
for {
old, ok := m.Load(key)
if !ok {
old = defaultValue
}
new := compute(old)
if m.CompareAndSwap(key, old, new) {
break
}
}This package provides syncmap.Map as a type alias to sync.Map. They are identical:
import (
"sync"
"golang.org/x/sync/syncmap"
)
// These are the same type
var m1 sync.Map
var m2 syncmap.Map
// Can assign freely
m1 = m2
m2 = m1You can use either import, but sync.Map from the standard library is more common in modern Go code. The syncmap package exists for historical compatibility, as this package was the original prototype for sync.Map.