Working with concurrent collections in Go has become an important topic due to the increasing demands of multithreaded applications. Regular maps in Go are not thread-safe and can lead to data races. The introduction of sync.Map provided a standard solution for safe shared access to collections without external synchronization.
Background:
Before the advent of sync.Map, developers had to use regular maps with an external Mutex or RWMutex to ensure safe access from multiple goroutines. This increased the amount of code and the likelihood of synchronization errors. In Go 1.9, sync.Map was introduced to simplify working with concurrent collections.
Problem:
A regular map is not thread-safe. If multiple goroutines read and write to the map without synchronization, it can lead to panics or unexpected results. Using Mutex correctly is complicated and can lead to deadlocks and performance degradation. There is also complexity with "double check" and dealing with hard-to-measure synchronization.
Solution:
sync.Map is a special structure from the standard library that provides thread-safe methods Load, Store, LoadOrStore, Delete, Range. It implements a lock-free strategy (partially) optimized for scenarios with frequent reads and rare writes.
Code example:
import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("foo", 42) value, ok := m.Load("foo") fmt.Println(value, ok) // 42 true m.Delete("foo") }
Key features:
Can all maps be replaced with sync.Map in multithreaded programs?
No, sync.Map is not a universal replacement for regular maps. It is well-suited for those data structures where concurrent, independent reads prevail, but with intensive writes (frequent modifications) or for small collections, regular maps + Mutex are faster and more efficient.
What happens if a regular map is used only for reading in multiple goroutines?
If the map is fully initialized and not modified after all goroutines start, parallel reading is permissible and safe. However, any deletion or modification of data will lead to unpredictable behavior, panic, or a corrupted map.
What types of data can be used as keys for sync.Map?
The rules are the same as for regular maps: only comparable types. However, sync.Map accepts a key of any interface{} type, which can create risks of objects with different semantics that cannot be compared with each other or may allow runtime errors.
Code example:
var m sync.Map m.Store([]int{1,2}, "value") // panic: runtime error: hash of unhashable type []int
A developer used sync.Map to store application settings that are rarely changed but frequently read. However, later, there began to be large writes of user session data, which led to an unexpected increase in GC load and performance degradation.
Pros:
Cons:
The team implemented sync.Map to store caches of frequently requested computation results in a high-load service. The number of "reads" exceeds "writes" by hundreds of times. Everything works stably and efficiently, and the code has become shorter and easier to maintain.
Pros:
Cons: