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
36: Detecting Race Conditions with the Race Detector
I once spent three days chasing a bug in a production service that only happened on Tuesdays. Not every Tuesday—just when traffic spiked. The logs were clean, the logic looked sound, but the final counts were always slightly off. It turns out I had a classic race condition. The terrifying thing about race conditions is that they are non-deterministic. Your code might work perfectly on your laptop, but the second it hits a multi-core server under load, it falls apart.
The bug that doesn't look like a bug
Let's look at a piece of code I've put together. Imagine we're building a simple hit counter for a website. We want to track how many times a page is visited across multiple concurrent requests.
package main
import (
"fmt"
"sync"
)
func main() {
var count int
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
count++ // This looks innocent, right?
}()
}
wg.Wait()
fmt.Printf("Final count: %d\n", count)
}
If I run this with go run main.go, I might see Final count: 1000. I might see 942. I might see 811. If I'm lucky (or unlucky), it works perfectly the first five times I run it, and I tell myself the code is fine. But count++ isn't an atomic operation; it's a read, an increment, and a write. When two goroutines do this at the exact same time, they both read the value 10, both increment it to 11, and both write 11 back. We just lost a visit.
Turning on the X-ray
Since we can't always rely on the bug manifesting during a standard run, we use the Race Detector. It's built directly into the Go toolchain. I don't have to install anything extra; I just add a flag to my command.
Let's try running that same program again, but this time with the -race flag:
go run -race main.go
Immediately, the output changes. Instead of a simple number, I get a wall of text that looks like a crash report. It tells me exactly what happened: WARNING: DATA RACE. It points to the specific line where the write occurred and the specific line where another goroutine was reading or writing to that same memory address.
I love this output because it doesn't just say "something is wrong." It gives me a stack trace for both competing goroutines. It's essentially the tool saying, "Hey, Goroutine A was trying to change this variable at the same time Goroutine B was touching it. You've got a problem here."
Cleaning up the mess
Now that the race detector has pointed the finger, I can't ignore it. To fix this, I need to ensure that only one goroutine can touch count at a time. A sync.Mutex is the most straightforward tool for the job here.
I'll adjust the code to lock the resource before the increment and unlock it immediately after.
package main
import (
"fmt"
"sync"
)
func main() {
var count int
var wg sync.WaitGroup
var mu sync.Mutex // Added a mutex to guard the count
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
count++
mu.Unlock()
}()
}
wg.Wait()
fmt.Printf("Final count: %d\n", count)
}
Now, I run it again with go run -race main.go. This time, the output is clean. No warnings, and the count is consistently 1000. The race detector is silent, which in this case, is exactly what we want.
A quick word of caution
You might be tempted to leave the -race flag on in your production environment. Don't. The race detector works by instrumenting your code, which adds significant overhead—usually increasing memory usage and slowing down execution by 2x to 20x. It's a powerhouse for testing and CI/CD pipelines, but it's not meant for the live environment.
📋 Practical Task
Fixing the Concurrent Map Crash
In Go, maps are not safe for concurrent use. If two goroutines write to a map at the same time, the runtime will actually panic and crash the program. However, sometimes you have a "read-write" race that doesn't cause an immediate panic but results in corrupted data.
Create a program where multiple goroutines are updating a shared map of "user scores". For example, have 10 goroutines each incrementing the score of a specific user 100 times.
Your requirements:
- Create a
map[string]intto hold scores. - Launch several goroutines that update the map concurrently.
- Run the program with
go run -raceand observe the data race warning. - Implement a
sync.Mutexto synchronize access to the map. - Verify that the race detector no longer reports any issues.
There are no comments for now.