Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
161: sync.Map for Concurrent Maps
I once worked with a developer who spent three straight days chasing a phantom crash that only happened in production. He had built a high-throughput API gateway and used a standard Go map to cache authentication tokens. To make it "thread-safe," he wrapped the map in a sync.RWMutex. On paper, it was perfect. But under heavy load, the service started panicking with fatal error: concurrent map read and map write. It turned out that in one obscure edge case—a token refresh logic—he had forgotten to acquire the lock before a write. Because Go maps are notoriously unforgiving about concurrent access, the whole process died instantly. He didn't need a bigger lock; he needed a different tool.
When the Mutex Becomes the Bottleneck
Now, don't get me wrong: a map with a sync.RWMutex is the right choice for 90% of your Go projects. It's explicit, it's fast, and you have total control over the types. But there's a specific pattern where that mutex actually starts slowing you down. When you have a map that is read from thousands of times per second, but only updated occasionally, the lock contention on that RWMutex can become a performance killer.
This is exactly why the Go team gave us sync.Map. It's a specialized concurrent map implementation that optimizes for two specific cases: when the entry for a given key is only ever written once but read many times, or when multiple goroutines read, write, and overwrite entries for disjoint sets of keys. It does this by using a clever combination of a read-only map and a "dirty" map to minimize lock contention.
Navigating the sync.Map API
The first thing you'll notice is that sync.Map doesn't use generics (it's an older part of the library). Everything goes in as an any and comes out as an any. This means you'll be doing a fair bit of type assertion. I'll be honest: it's a bit clunky compared to modern Go, but it's the price you pay for the internal lock-free optimizations.
package main
import (
"fmt"
"sync"
)
func main() {
var cache sync.Map
// Store a value
cache.Store("user_123", "Session_A")
// Load a value (requires type assertion)
if val, ok := cache.Load("user_123"); ok {
fmt.Println("Found session:", val.(string))
}
// LoadOrStore is the "magic" method.
// It loads the value if it exists, otherwise stores the provided value.
// This is critical for avoiding race conditions during cache initialization.
actual, loaded := cache.LoadOrStore("user_456", "Session_B")
fmt.Printf("Actual: %v, Loaded: %v\n", actual, loaded)
// Range iterates over the map.
// You return 'true' to continue, 'false' to stop.
cache.Range(func(key, value any) bool {
fmt.Printf("Key: %v, Value: %v\n", key, value)
return true
})
}
Choosing the Right Map for the Job
I see developers reach for sync.Map the moment they see the word "concurrent," but that's a mistake. Because it uses any and internal overhead to manage its two-map system, it can actually be slower than a standard map with a mutex if your workload involves constant writes to the same keys.
- Use
map+sync.RWMutexif you have a high volume of writes, or if you need the type safety of generics to avoid the overhead and risk of type assertions. - Use
sync.Mapif you have a "read-heavy" cache where keys are stable, or if different goroutines are operating on entirely different keys.
One last tip: if you find yourself using sync.Map and you're constantly casting types in every single function, consider wrapping the sync.Map in your own struct. Provide typed methods (like GetSession(id string) string) to hide the any casting from the rest of your business logic. Your future self will thank you.
📋 Practical Task
Implementing a Thread-Safe API Token Cache
You are building a middleware component that caches API tokens to avoid hitting a database on every request. Currently, the implementation uses a standard map and is crashing under load due to concurrent access.
Your Goal: Refactor the following code to use sync.Map. You must ensure that the GetToken method handles the type assertion correctly and that the SetToken method stores the value safely.
package main
import (
"fmt"
"sync"
)
type TokenCache struct {
// TODO: Replace the map and mutex with a sync.Map
mu sync.RWMutex
tokens map[string]string
}
func (c *TokenCache) SetToken(userID string, token string) {
// TODO: Implement using sync.Map Store
}
func (c *TokenCache) GetToken(userID string) (string, bool) {
// TODO: Implement using sync.Map Load with proper type assertion
return "", false
}
func main() {
cache := &TokenCache{
// Initialize your sync.Map here if necessary
}
var wg sync.WaitGroup
// Simulate 100 concurrent writers and readers
for i := 0; i < 100; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
user := fmt.Sprintf("user_%d", id%10)
token := fmt.Sprintf("token_%d", id)
cache.SetToken(user, token)
cache.GetToken(user)
}(i)
}
wg.Wait()
fmt.Println("Successfully processed concurrent requests.")
}
There are no comments for now.