How do you do a non-blocking send or receive?
Question 201MediumGo 1.22 to 1.25
Use select with a default case. If no other case is ready, default runs right away.
// Non-blocking send: drop if the consumer is behind (e.g. metrics, logs)
select {
case events <- e:
default:
dropped.Add(1)
}
// Non-blocking receive: poll
select {
case v := <-results:
use(v)
default:
// nothing yet
}
// Try-acquire on a semaphore channel
select {
case sem <- struct{}{}:
defer func() { <-sem }()
work()
default:
return ErrBusy
}
Pitfall: a non-blocking receive inside a tight for loop is a busy-wait that burns a whole CPU core. If you're polling, you almost always want a blocking select with a timeout or ctx case instead. Also, a non-blocking send on an unbuffered channel only succeeds if a receiver is already parked at that exact moment, which is often surprising.
More on Channels & select
- Q199Does
time.Afterin a loop leak memory? How did Go 1.23 change this? - Q200What's the difference between
time.Tickerand repeatedtime.After? What happens if the consumer is slow? - Q202What does this print? (select evaluation order)
- Q203How would you implement a semaphore / bounded concurrency with channels?
- Q204Implement a worker pool with result collection and proper shutdown.
- Q205What is a goroutine leak caused by channels? Show one and fix it.