← Back to Go Course | Chapter 11: Concurrency | Lesson 6 of 8

sync.Mutex

A mutex is like a single bathroom key -- only one goroutine can hold it and use the shared resource at a time, and everyone else has to wait their turn.
Syntax
go
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
// critical section

Protecting Shared State

A sync.Mutex ensures that only one goroutine at a time can execute the code between Lock() and Unlock(), preventing concurrent goroutines from corrupting shared data by modifying it simultaneously.

Example: Protecting Shared State

markup
// Every Go file belongs to a package; main builds an executable
package main

// Import the packages this program uses
import (
	"fmt"
	"sync"
)

// Define a struct type named Counter
type Counter struct {
	mu    sync.Mutex
	value int
}

// Define the method Increment on type Counter
func (c *Counter) Increment() {
	c.mu.Lock()
	// defer runs this call when the surrounding function returns
	defer c.mu.Unlock()
	c.value++
}

// main is where the program starts running
func main() {
	// Short declaration: create c and infer the type
	c := &Counter{}
	// Declare wg with the zero value of its type
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			c.Increment()
		}()
	}
	wg.Wait()
	fmt.Println(c.value)
}

Always Pair Lock with defer Unlock

Using defer immediately after Lock() guarantees Unlock() runs no matter how the function exits -- including early returns or a panic -- which avoids permanently locking out every other goroutine.

Example: Always Pair Lock with defer Unlock

markup
// Every Go file belongs to a package; main builds an executable
package main

// Import the packages this program uses
import (
	"fmt"
	"sync"
)

// Define a struct type named SafeMap
type SafeMap struct {
	mu   sync.Mutex
	data map[string]int
}

// Define the method Set on type SafeMap
func (m *SafeMap) Set(key string, value int) {
	m.mu.Lock()
	// defer runs this call when the surrounding function returns
	defer m.mu.Unlock()
	m.data[key] = value
}

// main is where the program starts running
func main() {
	// Short declaration: create m and infer the type
	m := &SafeMap{data: make(map[string]int)}
	m.Set("a", 1)
	// Print the values followed by a newline
	fmt.Println(m.data)
}

What Happens Without a Mutex

Without a mutex (or other synchronization), multiple goroutines reading and writing the same variable concurrently produce a data race -- an unpredictable, sometimes silently wrong result that can differ from run to run.

Note: Run the race detector ('go run -race') on real concurrent code to catch missing synchronization.

Example: What Happens Without a Mutex

markup
// Every Go file belongs to a package; main builds an executable
package main

// Import the packages this program uses
import (
	"fmt"
	"sync"
)

// main is where the program starts running
func main() {
	// Declare mu with the zero value of its type
	var mu sync.Mutex
	// Short declaration: create total and infer the type
	total := 0
	// Declare wg with the zero value of its type
	var wg sync.WaitGroup
	// Loop: repeat while the condition holds
	for i := 0; i < 50; i++ {
		wg.Add(1)
		// Start this call in a new goroutine (runs concurrently)
		go func() {
			defer wg.Done()
			mu.Lock()
			total++
			mu.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println("total:", total)
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Forgetting to call Unlock() after Lock(), permanently blocking every other goroutine waiting for that mutex.
  2. Locking a mutex, then returning early on an error path without unlocking -- always pair Lock() with a deferred Unlock().
  3. Copying a struct containing a sync.Mutex by value, which duplicates the lock and breaks the mutual exclusion guarantee.
Chapter Summary
  • sync.Mutex provides mutual exclusion: only one goroutine can hold the lock at a time.
  • Lock() acquires the mutex (blocking if another goroutine holds it), Unlock() releases it.
  • defer mu.Unlock() right after Lock() is the standard way to guarantee release, even on early returns.
  • A mutex protects shared data from concurrent, unsynchronized access that would otherwise cause a data race.
🔒

Chapter Quiz — Complete all 8 topics to unlock

0/8 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.