Synchronization Primitives

Back

Loading concept...

🔐 Go Synchronization Primitives: The Traffic Control Story

Imagine a busy city intersection. Cars (goroutines) are zooming everywhere. Without traffic lights and rules, there would be chaos and crashes. Go’s synchronization primitives are like the traffic control system—they keep everything safe and orderly!


🚗 The Big Picture: Why Do We Need Synchronization?

When multiple goroutines (like little workers) access the same data at the same time, bad things can happen. It’s like two kids trying to write on the same piece of paper at once—the result is a mess!

Synchronization primitives are the tools Go gives us to make sure:

  • Only one worker touches the data at a time
  • Workers wait for each other when needed
  • Everyone finishes before we move on

🔒 sync.Mutex — The Bathroom Lock

What Is It?

A Mutex is like a bathroom lock. Only ONE person can use the bathroom at a time. When someone is inside, they lock the door. Others must wait until they unlock it.

How It Works

import "sync"

var counter int
var mu sync.Mutex

func increment() {
    mu.Lock()       // Lock the door
    counter++       // Use the bathroom
    mu.Unlock()     // Unlock when done
}

Simple Rule

  • Lock() = “I’m going in, wait for me!”
  • Unlock() = “I’m done, next person can go!”

Real Life Example

Think of a piggy bank. Only one person can put money in at a time, or coins might fall out!


📖 sync.RWMutex — The Library Rule

What Is It?

RWMutex is like a library rule:

  • Many people can READ a book at the same time
  • But only ONE person can WRITE (edit) at a time
  • When someone is writing, nobody can read

How It Works

var data string
var rwmu sync.RWMutex

func readData() string {
    rwmu.RLock()        // Reading lock
    defer rwmu.RUnlock()
    return data
}

func writeData(s string) {
    rwmu.Lock()         // Writing lock
    defer rwmu.Unlock()
    data = s
}

When to Use It?

  • Use RWMutex when you read MORE than you write
  • It’s faster because many can read together!
graph TD A["RWMutex"] --> B["RLock - Many Readers OK"] A --> C["Lock - Only 1 Writer"] B --> D["Read Data"] C --> E["Write Data"]

🛎️ sync.Cond — The Dinner Bell

What Is It?

Cond (Condition Variable) is like a dinner bell. Workers wait until someone rings the bell to tell them “Time to eat!” or “Time to work!”

How It Works

var mu sync.Mutex
var cond = sync.NewCond(&mu)
var ready bool

// Worker waits for signal
func worker() {
    mu.Lock()
    for !ready {
        cond.Wait()  // Sleep until bell
    }
    // Do work
    mu.Unlock()
}

// Boss rings the bell
func boss() {
    mu.Lock()
    ready = true
    cond.Broadcast() // Wake everyone!
    mu.Unlock()
}

Key Methods

Method What It Does
Wait() Go to sleep, wait for bell
Signal() Wake up ONE waiter
Broadcast() Wake up ALL waiters

🗺️ sync.Map — The Thread-Safe Treasure Chest

What Is It?

A regular Go map is NOT safe when multiple goroutines use it. sync.Map is a special treasure chest that multiple workers can safely put things in and take things out of!

How It Works

var m sync.Map

// Store a treasure
m.Store("gold", 100)

// Get a treasure
value, ok := m.Load("gold")
if ok {
    fmt.Println("Found:", value)
}

// Delete a treasure
m.Delete("gold")

Key Methods

Method What It Does
Store(key, value) Put item in chest
Load(key) Get item from chest
Delete(key) Remove item
Range(func) Look at everything

When to Use?

  • When keys are written once, read many times
  • When goroutines work on different keys

⚠️ Race Conditions — The Scary Bug

What Is It?

A race condition is when two workers try to change the same thing at the same time. The result depends on who finishes first—like a race!

The Problem

var count = 0

// Two goroutines run this
func addOne() {
    count = count + 1
}

What can go wrong?

  1. Worker A reads count = 0
  2. Worker B reads count = 0
  3. Worker A writes count = 1
  4. Worker B writes count = 1

Expected: 2 | Got: 1 | Bug!

The Fix: Use a Mutex!

var mu sync.Mutex

func addOneSafe() {
    mu.Lock()
    count = count + 1
    mu.Unlock()
}
graph TD A["Race Condition"] --> B["Two workers"] B --> C["Same data"] C --> D["No protection"] D --> E["CRASH or Wrong Result"]

🔍 Race Detector — The Bug Hunter

What Is It?

Go has a built-in bug hunter called the Race Detector. It watches your program run and tells you if there’s a race condition!

How to Use It

go run -race myprogram.go
go test -race ./...
go build -race

What It Shows

WARNING: DATA RACE
Write by goroutine 7:
  main.addOne()
      main.go:10

Previous write by goroutine 6:
  main.addOne()
      main.go:10

Pro Tip

  • Always run tests with -race flag
  • It slows down your program, so use it in development only
  • Fix every race it finds!

⏳ sync.WaitGroup — The Roll Call

What Is It?

WaitGroup is like a teacher doing roll call. The teacher waits until ALL students arrive before starting class.

How It Works

var wg sync.WaitGroup

func worker(id int) {
    defer wg.Done()  // "Present!"
    fmt.Println("Worker", id)
}

func main() {
    for i := 1; i <= 3; i++ {
        wg.Add(1)      // Expect 1 student
        go worker(i)
    }
    wg.Wait()          // Wait for all
    fmt.Println("All done!")
}

Key Methods

Method What It Does
Add(n) Expect n more workers
Done() One worker finished
Wait() Block until all done

The Golden Rule

  • Call Add() BEFORE starting goroutine
  • Call Done() INSIDE the goroutine
  • Call Wait() when you need everyone
graph TD A["Main"] --> B["Add 3"] B --> C["Start Worker 1"] B --> D["Start Worker 2"] B --> E["Start Worker 3"] C --> F["Done"] D --> G["Done"] E --> H["Done"] F --> I["Wait completes"] G --> I H --> I I --> J["Continue"]

🎯 sync.Once — The “Just Once” Magic

What Is It?

sync.Once makes sure something happens exactly one time, no matter how many goroutines try to do it. It’s like a birthday candle—you only blow it out once!

How It Works

var once sync.Once
var config string

func loadConfig() {
    once.Do(func() {
        config = "loaded!"
        fmt.Println("Config loaded")
    })
}

func main() {
    for i := 0; i < 10; i++ {
        go loadConfig()
    }
    // "Config loaded" prints ONCE
}

Common Uses

  • Initialize something once (database, config)
  • Singleton pattern (only one instance)
  • Close a resource exactly once

Important!

  • The function inside Do() runs exactly once
  • Even if many goroutines call it
  • Even if the first call panics

🎬 Summary: Your Synchronization Toolkit

Tool Analogy Use When
Mutex Bathroom lock One at a time
RWMutex Library rule Many readers, few writers
Cond Dinner bell Wait for signal
sync.Map Safe treasure chest Concurrent map access
WaitGroup Roll call Wait for all workers
Once Birthday candle Run exactly once

Quick Decision Guide

graph TD A["Need to protect data?"] -->|Yes| B["More reads than writes?"] A -->|No| C["Need to wait?"] B -->|Yes| D["Use RWMutex"] B -->|No| E["Use Mutex"] C -->|For all workers| F["Use WaitGroup"] C -->|For a signal| G["Use Cond"] C -->|Just once| H["Use Once"]

🚀 You Did It!

You now understand Go’s synchronization toolkit! Remember:

  1. Race conditions are bugs — always protect shared data
  2. Use -race flag — let Go find the bugs for you
  3. Pick the right tool — Mutex for simple, RWMutex for reads, WaitGroup for waiting

Go forth and write safe, concurrent code! 🎉

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.