# Synchronization

Course: [Go](https://www.devtutorials.ai/learn/go) · Section: Concurrency
Source: https://www.devtutorials.ai/learn/go/concurrency/synchronization

> Coordinate goroutines safely: why synchronization is needed, sync.WaitGroup, sync.Mutex and RWMutex, sync/atomic, and how to detect and prevent race conditions.

## What Is Synchronization?

**Synchronization** = making goroutines work together safely: waiting for each other, and taking turns with shared data.

**Shared data** = a variable that more than one goroutine reads or changes.

**Simple lesson:** synchronization keeps goroutines in step, so shared data stays correct.

### A Real-World Example

Think of the class attendance register on the teacher's desk.

- Two monitors both want to mark students present.
- If both write in the same box at the same moment, the marks get messy.
- So the rule is: **only the monitor holding the pen may write**.
- The other monitor waits for the pen.

Now the mapping:

- **The register** = shared data.
- **The pen** = a mutex (a lock).
- **Waiting for the pen** = `Lock()` blocking until it is free.
- **Handing the pen back** = `Unlock()`.

You have met two ways to coordinate goroutines:

- `sync.WaitGroup` in **Goroutines**
- channels in **Channels**

This lesson adds **mutexes** and **atomic** operations for shared data.

## What You Will Learn

In this lesson, you will learn:

1. Why synchronization is needed, and how to coordinate goroutines.
2. `sync.WaitGroup` and its `Add()`, `Done()` and `Wait()`.
3. `sync.Mutex` and `sync.RWMutex` with `Lock()` and `Unlock()`.
4. Atomic operations with `sync/atomic`.
5. What race conditions are, how to detect them, and how to prevent them.

## Synchronization Fundamentals

### Why Synchronization Is Needed

`count++` looks like one step, but it is really three:

1. **read** the current value
2. **add** one
3. **write** the new value back

If two goroutines do this at the same time, one update can be lost.

Step through it. Then switch on the mutex.

Compare the two tabs:

- **No mutex:** both read 0, both write 1. The final count is 1, not 2.
- **With mutex:** B waits until A is done. The final count is 2.

### Coordinating Goroutines

There are two different needs:

- **Wait for goroutines to finish** → `sync.WaitGroup` or channels.
- **Take turns with shared data** → `sync.Mutex`, `sync.RWMutex` or `sync/atomic`.

**Simple lesson:** use WaitGroups to wait, and locks or atomics to share data safely.

## Wait Groups

### sync.WaitGroup

**WaitGroup** = a counter of goroutines still working.

### Add(), Done() and Wait()

| Call | Who calls it | What it does |
|---|---|---|
| `wg.Add(n)` | the starter, **before** `go` | counter + n |
| `wg.Done()` | each goroutine, at the end | counter - 1 |
| `wg.Wait()` | the waiter | blocks until the counter is 0 |

```go runnable title="Wait for every house to report"
package main

import (
	"fmt"
	"sync"
)

func main() {
	houses := []string{"Red", "Blue", "Green", "Yellow"}
	points := make([]int, len(houses))
	var wg sync.WaitGroup

	wg.Add(len(houses))
	for i, h := range houses {
		go func() {
			defer wg.Done()
			points[i] = len(h) * 10
		}()
	}
	wg.Wait()
	fmt.Println(points)
}
```

```text
[30 40 50 60]
```

- `wg.Add(len(houses))` adds all four at once.
- `defer wg.Done()` runs even if the goroutine returns early.

**Simple lesson:** `Add` before starting, `Done` when finished, `Wait` to block until all are done.

## Mutexes

### sync.Mutex

**Mutex** (mutual exclusion) = a lock that lets only **one** goroutine at a time into a piece of code.

**Critical section** = the code between `Lock()` and `Unlock()`.

### Lock() and Unlock()

Press **Play** to watch the lock.

```go runnable title="A safe counter type"
package main

import (
	"fmt"
	"sync"
)

type VoteBox struct {
	mu    sync.Mutex
	votes map[string]int
}

func (b *VoteBox) Add(name string) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.votes[name]++
}

func (b *VoteBox) Count(name string) int {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.votes[name]
}

func main() {
	box := &VoteBox{votes: make(map[string]int)}
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if i%3 == 0 {
				box.Add("Ravi")
			} else {
				box.Add("Asha")
			}
		}()
	}
	wg.Wait()
	fmt.Println("Asha:", box.Count("Asha"), "Ravi:", box.Count("Ravi"))
}
```

```text
Asha: 66 Ravi: 34
```

- The mutex sits **next to** the data it protects, inside the struct.
- `defer b.mu.Unlock()` makes sure the lock is always released.
- Maps are not safe for many goroutines writing at once, so the lock is required. See **Maps**.
- A struct with a mutex must be passed by pointer. A copy would have its own, separate lock.

### sync.RWMutex

**RWMutex** = a lock with two modes:

- **`RLock()` / `RUnlock()`** = many readers at the same time.
- **`Lock()` / `Unlock()`** = one writer, and no readers while it writes.

```go runnable title="Many readers, one writer"
package main

import (
	"fmt"
	"sync"
)

type Timetable struct {
	mu      sync.RWMutex
	periods map[string]string
}

func (t *Timetable) Get(day string) string {
	t.mu.RLock()
	defer t.mu.RUnlock()
	return t.periods[day]
}

func (t *Timetable) Set(day, subject string) {
	t.mu.Lock()
	defer t.mu.Unlock()
	t.periods[day] = subject
}

func main() {
	tt := &Timetable{periods: map[string]string{"Mon": "Maths"}}
	tt.Set("Tue", "Science")
	var wg sync.WaitGroup
	for _, d := range []string{"Mon", "Tue", "Mon"} {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_ = tt.Get(d)
		}()
	}
	wg.Wait()
	fmt.Println(tt.Get("Mon"), tt.Get("Tue"))
}
```

```text
Maths Science
```

- Use `RWMutex` when reads are much more common than writes.

**Simple lesson:** a mutex lets one goroutine at a time touch shared data, and an RWMutex lets many readers share.

## Atomic Operations

### sync/atomic

**Atomic operation** = a small change, like adding 1, that happens as **one** unbreakable step.

- No goroutine can see it half done.
- Faster than a mutex for simple counters.

```go runnable title="An atomic counter"
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var visitors atomic.Int64
	var wg sync.WaitGroup
	for i := 0; i < 500; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			visitors.Add(1)
		}()
	}
	wg.Wait()
	fmt.Println("Library visitors:", visitors.Load())
}
```

```text
Library visitors: 500
```

- `atomic.Int64` has methods: `Add`, `Load`, `Store`, `CompareAndSwap`.
- Older code uses functions instead: `atomic.AddInt64(&n, 1)`.
- Use atomics only for single numbers or flags. For anything bigger, use a mutex.

**Simple lesson:** use `sync/atomic` for a simple shared counter, and a mutex for anything more.

## Race Conditions

### What Are Race Conditions?

**Data race** = two goroutines use the same variable at the same time, and at least one of them writes.

**Race condition** = a bug where the result depends on which goroutine happens to run first.

```go runnable varies title="An unsafe counter (do not copy this)"
package main

import (
	"fmt"
	"sync"
)

func main() {
	count := 0
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			count++
		}()
	}
	wg.Wait()
	fmt.Println(count)
}
```

- On a real computer with many cores, this often prints a number **below** 1000, and a different one each run.
- In this lab, goroutines take turns on one thread, so you may see 1000. The code is still wrong.

### Detection

Go has a built-in **race detector**. Run your program or tests with `-race`:

```bash
go run -race main.go
go test -race ./...
```

```text
==================
WARNING: DATA RACE
Read at 0x00c000012118 by goroutine 8:
  main.main.func1()
      /school/main.go:14 +0x...
Previous write at 0x00c000012118 by goroutine 7:
  ...
==================
```

- It shows **where** the two goroutines touched the same memory.
- It needs the `go` tool on your computer, so it cannot run in this lab.
- Run your tests with `-race` often.

### Prevention

- **Do not share:** give each goroutine its own data, then combine results. See **Goroutines**.
- **Send instead of share:** pass values through channels. See **Channels**.
- **Lock:** protect shared data with a `sync.Mutex`.
- **Atomic:** use `sync/atomic` for single counters.
- **Check:** run with `-race`.

Common lock mistakes:

- Forgetting `Unlock()` freezes every other goroutine. Use `defer mu.Unlock()`.
- Locking the same mutex twice in one goroutine waits forever.
- Copying a struct that holds a mutex makes a second, unrelated lock.

**Easy way to remember**

- Wait for work → `WaitGroup`
- Hand data over → channels
- Share data → `Mutex` (`RWMutex` for many readers)
- Share one number → `atomic`
- Check → `go run -race`

**Simple lesson:** a data race happens when goroutines share data without rules, and locks, channels or atomics prevent it.

## TL;DR

1. **Synchronization** = goroutines waiting for each other and taking turns.
2. **WaitGroup** = `Add(n)` before `go`, `defer Done()`, `Wait()`.
3. **Mutex** = `Lock()` ... `defer Unlock()`. One goroutine at a time.
   - **Critical section** = the code between them.
4. **RWMutex** = `RLock` for many readers, `Lock` for one writer.
5. **Atomic** = `atomic.Int64`: `Add`, `Load`. For single numbers.
6. **Data race** = shared variable, at the same time, at least one write.
   - **Detect** = `go run -race`, `go test -race`.
   - **Prevent** = do not share, use channels, lock, or atomics.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `count++` from many goroutines | `++` is one step | It is read, add, write | Lost updates. |
| Forgetting `Unlock()` | The lock frees itself | Unlock, ideally with `defer` | Other goroutines wait forever. |
| Copying a struct with a mutex | The lock is shared | The copy has its own lock | No protection at all. |
| Locking around a slow network call | Locks are cheap | Keep critical sections short | Everything slows down. |
| `wg.Add` inside the goroutine | It counts in time | `Add` before `go` | `Wait` may return too early. |
| "It printed 1000, so it is safe" | One correct run proves it | Races show up only sometimes | Bugs in production. Use `-race`. |

## Check Yourself

## Exercises

## What to Remember

- Shared data needs rules when goroutines change it.
- `WaitGroup` waits. `Mutex` takes turns. `RWMutex` lets readers share. `atomic` changes one number safely.
- Always `defer mu.Unlock()`, and keep critical sections short.
- A data race may hide on one run and show on the next. Use `go run -race`.

Next: **Reflection**. You will learn how a Go program can look at its own values and types while it runs.
