Lab 03: sync Package

Time: 30 minutes | Level: Practitioner | Docker: docker run -it --rm golang:1.22-alpine sh

Overview

The sync package provides fundamental synchronisation primitives. When goroutines share mutable state, synchronisation prevents data races — detectable with Go's built-in race detector (go run -race).

Step 1: sync.Mutex — Exclusive Lock

Use Mutex when only one goroutine should access a resource at a time.

package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu sync.Mutex
    v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.v[key]++
}

func (c *SafeCounter) Value(key string) int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.v[key]
}

func main() {
    counter := &SafeCounter{v: make(map[string]int)}
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Inc("hits")
        }()
    }
    wg.Wait()
    fmt.Println("hits:", counter.Value("hits")) // always 1000
}

💡 Tip: Always use defer mu.Unlock() right after Lock() — it prevents forgetting to unlock on early return or panic.

Step 2: sync.RWMutex — Read-Write Lock

RWMutex allows many concurrent readers but only one writer — ideal for read-heavy caches.

Step 3: sync.WaitGroup

WaitGroup waits for a collection of goroutines to finish.

💡 Tip: Always call wg.Add(n) before spawning goroutines — calling it inside the goroutine races with wg.Wait().

Step 4: sync.Once — One-Time Initialization

Once guarantees a function executes exactly once, even under concurrent access.

Step 5: sync.Map — Concurrent Map

sync.Map is optimized for cases where keys are written once and read many times (e.g., caches, registries).

Step 6: Race Detector

The race detector finds concurrent access bugs at runtime.

Example of a race (DON'T DO THIS):

Fix: use sync.Mutex or sync/atomic.

Step 7: Combining Primitives

Step 8: Capstone — Thread-Safe LRU Cache Skeleton

📸 Verified Output:

Summary

Primitive
Use Case
Key Methods

sync.Mutex

Exclusive access to shared state

Lock(), Unlock()

sync.RWMutex

Read-heavy shared state

Lock/Unlock, RLock/RUnlock

sync.WaitGroup

Wait for N goroutines to finish

Add(n), Done(), Wait()

sync.Once

One-time initialization

Do(f)

sync.Map

Concurrent-safe map for cache-like patterns

Store, Load, Range, Delete

Race detector

Find data races at runtime

go run -race / go test -race

Last updated