- The Dev Loop
- Posts
- 🚀 The Golang Chronicle #5 – Sync Package Deep Dive
🚀 The Golang Chronicle #5 – Sync Package Deep Dive
Concurrency, Sync Patterns, and Go’s Evolving Ecosystem
đź”™ Recap & Continuity
"In the Previous newsletter, we explored sync.Mutex
, WaitGroup
, and Pool
. This post, we dive into advanced patterns, Go 1.22’s sync
updates, and real-world code examples."
đź“° Latest News
Go 1.22’s
sync
Additions
Building onsync.Pool
optimizations (from #4), Go 1.22 introducesOnceFunc
andOnceValue
for safer one-time initializations. Example:var initConfig = sync.OnceValue(func() *Config { return loadConfig() // Called exactly once })
Critical Patch for
sync/atomic
Go 1.21.4 fixes a rare race condition in atomic operations—upgrade immediately if using low-level concurrency.📚 Sync Package Deep Dive, Part II
📚 Sync Package Deep Dive, Part II
1. Advanced sync.Cond
(Condition Variables)
Build a thread-safe task queue with producer-consumer coordination:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var (
mu sync.Mutex
cond = sync.NewCond(&mu)
queue []int
)
// Consumer
go func() {
for {
mu.Lock()
for len(queue) == 0 {
cond.Wait() // Releases mutex, waits for Signal/Broadcast
}
task := queue[0]
queue = queue[1:]
mu.Unlock()
fmt.Printf("Processed task %d\n", task)
}
}()
// Producer
for i := 0; i < 5; i++ {
mu.Lock()
queue = append(queue, i)
cond.Signal() // Wake one consumer
mu.Unlock()
time.Sleep(100 * time.Millisecond)
}
}
Output:
Processed task 0
Processed task 1
Processed task 2
Processed task 3
Processed task 4
2. Testing sync.Once
Safely
Reset sync.Once
for test reproducibility (use with caution!):
package main
import (
"sync"
"sync/atomic"
"unsafe"
)
type Once struct {
sync.Once
}
// Reset allows re-initialization for testing purposes
func (o *Once) Reset() {
atomic.StoreUint32(
(*uint32)(unsafe.Pointer(&o.Once)),
0,
)
}
func main() {
var once Once
once.Do(func() { println("Initialized") }) // Runs
once.Reset()
once.Do(func() { println("Re-initialized") }) // Runs again
}
3. sync.Map
in Distributed Systems
CockroachDB’s pattern for sharded sync.Map
usage:
package main
import (
"sync"
)
const shards = 64
type ShardedMap struct {
maps [shards]*sync.Map
}
func NewShardedMap() *ShardedMap {
sm := &ShardedMap{}
for i := 0; i < shards; i++ {
sm.maps[i] = &sync.Map{}
}
return sm
}
func (sm *ShardedMap) Get(key string) (interface{}, bool) {
return sm.shard(key).Load(key)
}
func (sm *ShardedMap) shard(key string) *sync.Map {
// Simple hash to distribute keys
sum := 0
for _, c := range key {
sum += int(c)
}
return sm.maps[sum%shards]
}
// Usage:
func main() {
sm := NewShardedMap()
sm.shard("user:123").Store("user:123", "Alice")
value, _ := sm.Get("user:123")
println(value.(string)) // Output: "Alice"
}
🛠️ Tools & Libraries
1. conc
for Structured Concurrency
Safer WaitGroup
with panic handling:
package main
import (
"github.com/sourcegraph/conc"
)
func main() {
pool := conc.NewWaitGroup()
pool.Go(func() {
defer func() {
if r := recover(); r != nil {
println("Recovered from panic:", r)
}
}()
panic("oops!")
})
pool.Wait() // No crash! Panic is recovered
}
2. go-deadlock
Detection
Catch mutex deadlocks in tests:
package main_test
import (
"testing"
"github.com/sasha-s/go-deadlock"
)
func TestDeadlock(t *testing.T) {
var mu deadlock.Mutex
mu.Lock()
// Forgetting to unlock here would trigger a deadlock warning
defer mu.Unlock()
}
đź’ˇ Tip of the Week: Benchmark RWMutex
vs `Mutex
package main
import (
"sync"
"testing"
)
func BenchmarkMutex(b *testing.B) {
var mu sync.Mutex
data := make(map[int]int)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
data[0] = 1 // Write-heavy workload
mu.Unlock()
}
})
}
func BenchmarkRWMutex(b *testing.B) {
var mu sync.RWMutex
data := make(map[int]int)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
data[0] = 1 // Same workload
mu.Unlock()
}
})
}
Results:
BenchmarkMutex-8 85634902 13.8 ns/op
BenchmarkRWMutex-8 58987820 20.1 ns/op
Conclusion: Mutex
outperforms RWMutex
for write-heavy workloads.
📢 Call to Action
Vote for Next Issue’s Topic:
context.Context
+sync
IntegrationHow to propagate cancellation across goroutines using
context
andWaitGroup
.
Channels vs Mutexes: Performance Deep Dive
When to use channels for synchronization vs mutexes.
Quick Reminder:
If you missed our first issue, don’t worry! You can always catch up on The GoLang Chronicle Archive, where we’ve got all the previous editions waiting for you.
I’d love to hear your thoughts on what you’d like to see in future issues. Do you have specific topics or challenges you want covered? Hit reply and let me know!
Join the GoLang Community!
Go Community https://thedevloop.beehiiv.com/subscribe
I’m so excited to start this journey with you! Whether you're a newbie or a Go master, there’s always something new to learn, and The GoLang Chronicle will be your guide along the way.
Until next time, happy coding! đź’»
Cheers,
Aravinth Veeramuthu