Explanation
What just happened
main started two workers, sent four jobs into a buffered channel and closed it. Each worker ranged over jobs: take a job, do it, take the next. Which worker got which job was up to timing.
close(jobs) doesn’t throw jobs away. It means “no more are coming”. The workers finished the jobs already in the buffer, then their loops ended, they called Done, and wg.Wait() returned.
Try the what-if switch
- forgot close: the workers finished all four jobs, then waited in
rangefor a fifth that never came.mainwaited for them. With every goroutine stuck, Go stopped the program:fatal error: all goroutines are asleep - deadlock!, exit code 2.
Why the pool shape works
The channel is the queue and the lock in one. Each job is received by exactly one worker, so no job runs twice, and you change the pool size by changing one number.
Watch out
Go only reports a deadlock when every goroutine is stuck. Its dump shows where each one was waiting, like the lanes here.
Common questions
How does range over a channel end?
for j := range jobs ends when jobs is closed and every value in it has been received. If nobody closes it, the loop waits forever in [chan receive].
Who should close a channel?
The sender, once it has sent its last value. Sending on a closed channel panics with send on closed channel.
Which worker gets which job?
Whichever is free when the job is received. It can change on every run, so don't depend on it.
Verified: this timeline matches a real program, checked in CI on Go 1.26 and 1.27. How we verify. Unclear or wrong? Tell us