Print odd and even numbers alternately from two goroutines (1..N) using channels. Then do the same with sync.Mutex and sync.Cond.
Channels: pass a "token" back and forth over two unbuffered channels. Only the goroutine holding the token may print, so the order is fixed. Each goroutine passes the token on only if more numbers are left. This matters: without the i < n check, the last send has no receiver and the program deadlocks.
func oddEvenChan(n int) {
odd, even := make(chan struct{}), make(chan struct{})
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 1; i <= n; i += 2 {
<-odd
fmt.Println(i)
if i < n {
even <- struct{}{}
}
}
}()
go func() {
defer wg.Done()
for i := 2; i <= n; i += 2 {
<-even
fmt.Println(i)
if i < n {
odd <- struct{}{}
}
}
}()
odd <- struct{}{} // kick off
wg.Wait()
}
Mutex + Cond: both goroutines share a counter next. Each one waits in a loop until it is its turn. You need the loop because of spurious wakeups and because Broadcast wakes everyone.
func oddEvenCond(n int) {
var mu sync.Mutex
cond := sync.NewCond(&mu)
next := 1
var wg sync.WaitGroup
worker := func(parity int) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
for {
for next <= n && next%2 != parity {
cond.Wait() // atomically unlocks mu, sleeps, relocks
}
if next > n {
cond.Broadcast() // let the other goroutine see the end
return
}
fmt.Println(next)
next++
cond.Broadcast()
}
}
wg.Add(2)
go worker(1)
go worker(0)
wg.Wait()
}
What the interviewer is looking for: a clean exit with no leaked or deadlocked goroutine, Wait inside a for (never an if), and the explanation that Signal is enough with exactly two waiters but Broadcast is safer when there are more. Point out that the channel version is idiomatic, while the Cond version generalizes to "k goroutines printing in round-robin".
More on Classic Concurrency Coding Problems
- Q517Implement ping-pong between two goroutines that stops cleanly after N rounds or on context cancellation.
- Q518Print "foo", "bar", "baz" in strict order from three goroutines, repeated N times.
- Q519Implement the dining philosophers problem in Go without deadlock or starvation. Compare resource ordering with an arbiter.
- Q520Bank transfer: two goroutines transfer money between accounts A->B and B->A with per-account mutexes. Why does it deadlock, and how does consistent lock ordering fix it?
- Q521Implement a bounded blocking queue (producer-consumer) using sync.Cond, then using channels. Compare the two.
- Q522Implement a reusable barrier (CyclicBarrier) that N goroutines wait on before moving to the next phase.