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
163: RWMutex vs Mutex
I've seen this a lot in code reviews: a developer swaps out every single sync.Mutex for a sync.RWMutex, thinking they've just given the application a free performance boost. The logic seems sound—if multiple goroutines can read the data at once, the program should run faster, right?
The "RWMutex is always faster" Fallacy
Here is the cold truth: RWMutex is more complex internally than a standard Mutex. A simple Mutex is essentially a binary flag. An RWMutex, however, has to keep track of how many readers are currently holding the lock, handle writer queues, and manage the hand-off between readers and writers. This bookkeeping isn't free; it costs CPU cycles.
If your "read" operation is something trivial—like returning a single integer from a struct—the overhead of updating the reader count in an RWMutex can actually take longer than just waiting a few nanoseconds for a standard Mutex to unlock. I once profiled a service where switching back to a plain Mutex for a simple counter actually reduced latency by about 5%.
// This looks "optimized," but if GetValue is called
// millions of times per second and does almost nothing,
// the RWMutex overhead might actually slow you down.
type Config struct {
sync.RWMutex
value int
}
func (c *Config) GetValue() int {
c.RLock()
defer c.RUnlock()
return c.value
}
Matching the Lock to Your Access Pattern
So, when should you actually reach for the RWMutex? The answer is read-heavy contention. You want an RWMutex when you have a high ratio of reads to writes AND the read operation takes a non-trivial amount of time (like iterating over a map or performing a complex calculation).
Imagine you're building a shared cache of API tokens. You update the token once every hour (a write), but you read it for every single outgoing request (thousands of reads per second). In this scenario, if you used a standard Mutex, every single request would have to wait in a single-file line just to read a string. That's where RLock() shines. It allows a crowd of readers to enter the critical section simultaneously, as long as no one is trying to change the data.
Here is the pattern I follow:
- Use
sync.Mutexby default. It's simpler, faster for low-contention or fast operations, and harder to mess up. - Switch to
sync.RWMutexonly when you've identified a bottleneck where many goroutines are blocking each other just to read the same data.
One last warning: be careful with RLock. If you call RLock() and then, inside that same goroutine, try to call Lock() to update the value, you'll hit a deadlock. You cannot "upgrade" a read lock to a write lock in Go. You have to RUnlock() first, then Lock().
📋 Practical Task
Refactoring the Global Feature Flag Registry
You have been handed a FeatureRegistry struct used by a high-traffic web server. Currently, it uses a sync.Mutex. The registry is read on every single incoming HTTP request to check if a feature is enabled, but it is only updated once every few hours via an admin panel.
The current implementation is causing a bottleneck because thousands of request goroutines are blocking each other just to read the flags map.
Your Task:
- Change the
sync.Mutexto async.RWMutex. - Update the
IsEnabledmethod to use the appropriate "read" locking mechanism so that multiple requests can check flags concurrently. - Ensure the
SetFlagmethod continues to use the exclusive lock to prevent data races during updates.
package main
import (
"fmt"
"sync"
)
type FeatureRegistry struct {
mu sync.Mutex // CHANGE THIS
flags map[string]bool
}
func (r *FeatureRegistry) IsEnabled(feature string) bool {
r.mu.Lock() // OPTIMIZE THIS
defer r.mu.Unlock()
return r.flags[feature]
}
func (r *FeatureRegistry) SetFlag(feature string, enabled bool) {
r.mu.Lock()
defer r.mu.Unlock()
r.flags[feature] = enabled
}
func main() {
reg := &FeatureRegistry{
flags: make(map[string]bool),
}
reg.SetFlag("beta-ui", true)
fmt.Printf("Beta UI enabled: %v\n", reg.IsEnabled("beta-ui"))
}
There are no comments for now.