BudiBadu Logo

Samplebadu

Code with Example
BudiBadu Logo
Samplebadu

Go by Example: Mutexes

Go 1.23

Safely access shared state across multiple goroutines using mutual exclusion locks. This example demonstrates using `sync.Mutex` to prevent race conditions when incrementing a shared counter.

Code

package main

import (
    "fmt"
    "sync"
)

type Container struct {
    mu       sync.Mutex
    counters map[string]int
}

func (c *Container) inc(name string) {
    // Lock the mutex before accessing counters
    c.mu.Lock()
    // Ensure the mutex is unlocked when the function exits
    defer c.mu.Unlock()
    
    c.counters[name]++
}

func main() {
    c := Container{
        counters: map[string]int{"a": 0, "b": 0},
    }

    var wg sync.WaitGroup

    // Increment a named counter in a loop
    doIncrement := func(name string, n int) {
        for i := 0; i < n; i++ {
            c.inc(name)
        }
        wg.Done()
    }

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

    wg.Wait()
    fmt.Println(c.counters)
}

Explanation

In concurrent programming, race conditions occur when multiple goroutines access shared memory simultaneously without synchronization. Go's sync.Mutex (Mutual Exclusion lock) provides a way to ensure that only one goroutine can access a critical section of code at a time. By explicitly locking and unlocking the mutex, you protect shared resources (like maps, slices, or counters) from corruption.

Using Mutex correctly involves wrapping sensitive operations between Lock() and Unlock() calls. A common idiom is to use defer mu.Unlock() immediately after locking to guarantee the lock is released even if the function panics or returns early. While channels are often preferred for passing data, Mutexes are the standard tool for managing shared state in structs.

Important considerations:

  • Critical Section: The code between Lock and Unlock should be as short as possible to minimize contention.
  • Deadlocks: Forgetting to unlock or locking the same mutex twice in the same goroutine will cause the program to hang.
  • Pass by Pointer: Mutexes must not be copied; always pass them by pointer or embed them in a struct passed by pointer.

Code Breakdown

15
c.mu.Lock() acquires the lock. If another goroutine already holds the lock, this call blocks until the lock is available.
17
defer c.mu.Unlock() ensures the lock is released when the function returns. Forgetting to unlock will cause a deadlock.
19
Accessing the map is now safe because we hold the lock. Without the mutex, concurrent writes to the map would cause a runtime panic.
27
We use a WaitGroup here to wait for all goroutines to finish their increments before printing the final result.