Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
32: Sync Package: WaitGroups and Mutexes
I want to show you a snippet of code that looks perfectly fine at a glance. It's a common pattern: you have a list of tasks, you fire off a goroutine for each one to speed things up, and you collect the results in a map. But if you run this, Go will likely scream at you with a fatal error.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
urls := []string{"google.com", "github.com", "golang.org", "stackoverflow.com"}
results := make(map[string]string)
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
// Simulate a network request
time.Sleep(time.Millisecond * 10)
results[u] = "OK"
}(url)
}
wg.Wait()
fmt.Println("Finished:", results)
}
The Panic: Concurrent Map Writes
If you run the code above, you'll probably see fatal error: concurrent map writes. This is one of the most important lessons in Go concurrency: Maps are not thread-safe.
While the sync.WaitGroup is doing its job correctly—making sure the main function doesn't exit before the goroutines finish—it does nothing to protect the data inside those goroutines. When two or more goroutines try to write to the results map at the exact same time, Go panics to prevent memory corruption. It's a hard crash because there's no safe way to recover once the map's internal structure is compromised.
Protecting State with Mutexes
To fix this, we need a way to ensure that only one goroutine can touch the map at a time. This is where sync.Mutex (short for "mutual exclusion") comes in. Think of a Mutex as a physical key to a room. If you want to enter the room (access the map), you have to take the key. If someone else already has it, you have to wait in line until they put it back.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
urls := []string{"google.com", "github.com", "golang.org", "stackoverflow.com"}
results := make(map[string]string)
var wg sync.WaitGroup
var mu sync.Mutex // This is our "key"
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
time.Sleep(time.Millisecond * 10)
mu.Lock() // Grab the key
results[u] = "OK" // Safe to write now
mu.Unlock() // Give the key back
}(url)
}
wg.Wait()
fmt.Println("Finished:", results)
}
By wrapping the map assignment in mu.Lock() and mu.Unlock(), we've created a "critical section." Only one goroutine can execute the code between the lock and unlock at any given moment. I usually recommend keeping the locked section as small as possible. Notice I didn't put the time.Sleep inside the lock—that would defeat the whole purpose of using goroutines because everything would happen sequentially.
Coordinating the Finish Line with WaitGroups
You've already seen sync.WaitGroup in action here, but let's be explicit about why we use it. Without wg.Wait(), the main function would hit the end of its block and terminate the process immediately. It wouldn't care that there are four goroutines still sleeping or writing to a map; it would just kill them instantly.
Here are the three golden rules I follow when using WaitGroups:
- Add before you go: Always call
wg.Add(1)in the parent goroutine, before thegokeyword. If you putwg.Add(1)inside the goroutine itself, there's a race condition wherewg.Wait()might be reached before the goroutine even starts, causing the program to exit prematurely. - Defer the Done: Use
defer wg.Done()at the very top of your goroutine. This ensures that even if your function panics or returns early via an error check, the counter is still decremented. Otherwise, your program will deadlock forever. - Pass by Pointer: If you pass a
WaitGroupinto another function, you must pass it as a pointer (*sync.WaitGroup). If you pass it by value, the function gets a copy of the WaitGroup, and callingDone()on that copy won't affect the original counter inmain.
📋 Practical Task
Exercise: Build a Concurrent Portfolio Value Aggregator
You are building a tool that fetches the current price of several cryptocurrencies from a mock API and calculates the total value of a user's portfolio.
Requirements:
- Create a
portfoliomap where the key is the coin symbol (e.g., "BTC", "ETH") and the value is the amount owned (e.g., 0.5, 10.0). - Create a
pricesmap to store the fetched price for each coin. - Write a function
fetchPrice(coin string) float64that simulates a network delay usingtime.Sleepand returns a random float. - Use a
sync.WaitGroupto fetch prices for all coins in the portfolio concurrently. - Use a
sync.Mutexto safely write the fetched prices into thepricesmap. - Once all fetches are complete, calculate the total portfolio value (sum of
amount * pricefor all coins) and print it to the console.
There are no comments for now.