Interview Question: Thread-Safe Implementation of Map in Go
This blog demonstrates two approaches to implement a thread-safe map in Go: using sync.Mutex
and the built-in sync.Map
. These techniques ensure safe concurrent access to maps in multi-threaded environments.
This question was asked to me by Radisys for the role of Golang Developer.
Approach 1: SafeMap with sync.Mutex
This implementation uses sync.Mutex
to synchronize access to a map.
Structure and Methods
1. SafeMap Struct
Contains:
mu sync.Mutex
: The lock to synchronize map access.data map[string]int
: The underlying map to store key-value pairs.
2. Methods
-
NewSafeMap():
Creates and returns a new instance ofSafeMap
with an initialized map. -
Set(key string, value int):
Adds or updates a key-value pair in the map:- Acquires a lock (
mu.Lock()
). - Modifies the map.
- Releases the lock (
mu.Unlock()
).
- Acquires a lock (
-
Get(key string) (int, bool):
Retrieves the value for a given key:- Acquires a lock.
- Checks if the key exists.
- Returns the value and a boolean indicating the key’s existence.
- Releases the lock.
Code
package main
import (
"fmt"
"sync"
)
type SafeMap struct {
mu sync.Mutex
data map[string]int
}
func NewSafeMap() *SafeMap {
return &SafeMap{
data: make(map[string]int),
}
}
func (m *SafeMap) Set(key string, value int) {
m.mu.Lock()
defer m.mu.Unlock()
m.data[key] = value
}
func (m *SafeMap) Get(key string) (int, bool) {
m.mu.Lock()
defer m.mu.Unlock()
value, ok := m.data[key]
return value, ok
}
func main() {
m := NewSafeMap()
m.Set("a", 42)
value, ok := m.Get("a")
if ok {
fmt.Println("Value:", value)
} else {
fmt.Println("Key not found")
}
}
Approach 2: Using sync.Map
The sync.Map
is a concurrent map provided by Go’s sync
package, optimized for high-concurrency use cases. It eliminates the need for manual locking.
Characteristics
- Thread-safe: Multiple goroutines can access it safely.
- No Generics: Stores
interface{}
types, requiring type casting. - Optimized for Writes: Performs better in high-update scenarios.
- Iterate with Care: Iteration doesn’t guarantee order or consistency during modifications.
Common Methods
Store(key, value)
: Adds or updates a key-value pair.Load(key)
: Retrieves the value for a key.Delete(key)
: Removes a key-value pair.LoadOrStore(key, value)
: Loads an existing value or stores and returns a new one.Range(func(key, value any) bool)
: Iterates over all entries.
Code Example
package main
import (
"fmt"
"sync"
)
func main() {
var sm sync.Map
// Store values
sm.Store("a", 1)
sm.Store("b", 2)
// Load values
if value, ok := sm.Load("a"); ok {
fmt.Println("Key 'a':", value)
}
// Iterate
sm.Range(func(key, value any) bool {
fmt.Println("Key:", key, "Value:", value)
return true // Continue iteration
})
// Delete a key
sm.Delete("b")
}
Use Cases
-
sync.Mutex
+ Map:- When you need full control over locking.
- For simpler, low-concurrency use cases.
-
sync.Map
:- High-concurrency applications with frequent reads and writes.
- Caches or shared state in concurrent systems.
Conclusion
Both sync.Mutex
and sync.Map
are excellent tools for creating thread-safe maps in Go. Choose sync.Mutex
for full control and type safety or sync.Map
for simplicity and performance in high-concurrency scenarios.