Lock and Load

A Guide to Mutexes in Go

Featured image

Concurrency is one of the key features of Go (Golang), and with it comes the challenge of safely accessing shared resources. In this post, we’ll explore how to use Mutex (mutual exclusion) to manage concurrent access and prevent race conditions.

What is a Mutex?

A Mutex in Go is a synchronization primitive from the sync package. It ensures that only one goroutine can access a critical section of code at a time, preventing race conditions when multiple goroutines try to read or write shared data concurrently.

How Does a Mutex Work?

Basic Usage Example

Let’s look at a simple example to demonstrate the usage of Mutex in Go:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    var mu sync.Mutex
    counter := 0

    // Function to increment the counter
    increment := func() {
        defer wg.Done()
        mu.Lock()
        counter++
        mu.Unlock()
    }

    // Launch 1000 goroutines to increment the counter
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go increment()
    }

    // Wait for all goroutines to finish
    wg.Wait()

    fmt.Println("Final counter value:", counter)
}

Breaking It Down

1. Importing Packages

import (
    "fmt"
    "sync"
)

We start by importing the necessary packages. fmt is for formatting strings and printing, while sync provides synchronization primitives, including Mutex and WaitGroup.

2. Main Function Setup

func main() {
    var wg sync.WaitGroup
    var mu sync.Mutex
    counter := 0

Inside the main function, we declare:

3. Defining the Increment Function

increment := func() {
    defer wg.Done()
    mu.Lock()
    counter++
    mu.Unlock()
}

Here, we define an anonymous function called increment. This function will:

4. Launching Goroutines

for i := 0; i < 1000; i++ {
    wg.Add(1)
    go increment()
}

We then start 1000 goroutines, each running the increment function. Before launching each goroutine, we call wg.Add(1) to increment the WaitGroup counter.

5. Waiting for Completion

wg.Wait()

After launching all the goroutines, we call wg.Wait() to block the main function until all goroutines have finished executing. This ensures that the fmt.Println line doesn’t execute until all increments are done.

6. Printing the Result

fmt.Println("Final counter value:", counter)

Finally, we print the value of counter. With the Mutex ensuring that each increment operation is atomic, we expect the output to be 1000.

By using the Mutex, we ensure that the increments to the counter are performed safely, preventing race conditions.

Why Use Mutex?

Using a Mutex prevents race conditions by ensuring that only one goroutine can access the critical section of code at a time. In our example, this critical section is the increment operation on counter.

Important Considerations

The sync package provides other types of synchronization primitives like RWMutex, which allows multiple readers or one writer, and WaitGroup for waiting for a collection of goroutines to finish their execution.

Conclusion

Using Mutex in Go is a powerful way to manage concurrent access to shared resources. It helps prevent race conditions and ensures the integrity of your data. Remember to use it carefully to avoid deadlocks and performance issues. Happy coding!