SyntaxStudy
Sign Up
Go Goroutines with sync.WaitGroup and sync.Mutex
Go Beginner 1 min read

Goroutines with sync.WaitGroup and sync.Mutex

The `sync` package provides low-level synchronisation primitives. `sync.WaitGroup` is used to wait for a collection of goroutines to finish. You call `Add` before starting each goroutine, `Done` at the end of each goroutine (typically via defer), and `Wait` to block until the counter reaches zero. Always call `Add` before launching the goroutine to avoid a race condition. When multiple goroutines access shared mutable state, a data race occurs unless access is synchronised. `sync.Mutex` provides mutual exclusion: only one goroutine can hold the lock at a time. The `Lock` method acquires the lock and `Unlock` releases it. Using `defer mu.Unlock()` immediately after `mu.Lock()` is the safest pattern because it guarantees the lock is released even if the function panics or returns early. `sync.RWMutex` is a variant that allows multiple concurrent readers but only one writer. It is more efficient than a plain Mutex when reads vastly outnumber writes. The Go race detector (`go test -race` or `go run -race`) can detect data races at runtime, making it an essential development tool.
Example
package main

import (
    "fmt"
    "sync"
)

// SafeCounter is safe for concurrent use.
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]
}

// ReadCache uses RWMutex for read-heavy workloads.
type ReadCache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (rc *ReadCache) Set(k, v string) {
    rc.mu.Lock()
    defer rc.mu.Unlock()
    rc.data[k] = v
}

func (rc *ReadCache) Get(k string) (string, bool) {
    rc.mu.RLock()
    defer rc.mu.RUnlock()
    v, ok := rc.data[k]
    return v, ok
}

func main() {
    c := SafeCounter{v: make(map[string]int)}
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            c.Inc("hits")
        }()
    }
    wg.Wait()
    fmt.Println("hits:", c.Value("hits")) // 100

    cache := ReadCache{data: make(map[string]string)}
    cache.Set("lang", "Go")
    if v, ok := cache.Get("lang"); ok {
        fmt.Println("cached:", v)
    }
}