Lesson 16 · Concurrency & context
sync primitives
When you do share memory — Mutex, WaitGroup, Once.
Your win: use Mutex, WaitGroup, and
Once correctly, and answer the classic question — "channels or a mutex?"
The three you'll actually use
sync.Mutex
Guards shared state. Lock() before touching it, Unlock()
after (usually defer mu.Unlock()). RWMutex allows many
concurrent readers.
sync.WaitGroup
Wait for a set of goroutines. Add(n) before launching,
Done() as each finishes, Wait() to block until all are done.
sync.Once
Do(f) runs f exactly once, ever — for lazy init or an
idempotent "close this channel only once".
Remember Lesson 2: their zero values are ready —
var mu sync.Mutex works with no init.
Your Kafka engine uses all three
internal/golibs/kafka/kafka.go · conversationmgmt/.../taskrunner/task_runner.go// WaitGroup: await every consumer goroutine before shutting down
k.consumerWaitGroup.Add(1) ... k.consumerWaitGroup.Wait() // kafka.go:587,825
// Mutex: guard the shared "which consumers are running" state
k.mutex.Lock(); member.setRunning(false); k.mutex.Unlock() // kafka.go:590
// Once: close a channel exactly once, even if reached from many paths
closeOnce.Do(func() { close(ch) }) // task_runner.go
Once for channel close
Closing an already-closed channel panics (Lesson 15). When several code paths might
all try to close it, sync.Once makes the close idempotent — a small,
idiomatic guard our task_runner and worker pool both use.
Channels or a mutex? (the interview question)
Our engine shows both in one place: messages flow through channels, but the "is this consumer running?" map is shared state, so it's guarded by a mutex. Forcing everything through channels there would be more code, not less.
Mutex is not reentrant — locking it twice in the same goroutine
deadlocks. And never copy a struct containing a Mutex (copies the
lock state); that's why such structs are passed by pointer (Lesson 3). go vet
catches the copy mistake.
Effective Go — Concurrency + the sync package
Effective Go frames the channel-vs-mutex choice; the sync docs give the
exact semantics of each primitive.
Check yourself (from memory)
Q1. sync.WaitGroup is for…
Add/Done/Wait —
block until a set of goroutines completes. Our shutdown waits on the consumer group.
Q2. sync.Once's Do(f) guarantees f…
Q3. Prefer a mutex over a channel when you…
RWMutex, sync.Map, or atomic and when
each earns its place over a plain Mutex? Ask me.
1. Effective Go — concurrency; sync package docs; Go Wiki — Mutex or Channel.