What are the scheduling implications of runtime.Goexit, blocking in init, and unbuffered sends from many goroutines?
Question 178HardGo 1.22 to 1.25
runtime.Goexit()ends the calling goroutine after running all its deferred calls. Arecoverin those deferred calls returns nil, because Goexit is not a panic.t.FailNowrelies on it, which is why callingt.Fatalfrom a goroutine the test started is wrong: it exits that goroutine, not the test.- Package
initruns in one goroutine beforemain. Goroutines started ininitdo run, but ifinitblocks waiting on something that depends onmain, the program deadlocks. Keepinitfree of side effects and do not block in it. - Many senders on an unbuffered channel form a FIFO queue of sudogs in
sendq. The receiver wakes them in order, one handoff at a time. That is fair, but it serializes the senders, and each wake-up costs a scheduler round trip. A buffered channel sized for the typical burst lowers wake-up overhead. It does not add throughput if the consumer is the bottleneck.
func TestX(t *testing.T) {
errc := make(chan error, 1)
go func() { errc <- doWork() }() // report back, don't t.Fatal here
if err := <-errc; err != nil {
t.Fatal(err)
}
}More on Goroutines & the Scheduler
- Q176Is creating a goroutine per request (e.g., in net/http) a good design? What are the risks?
- Q177How are timers handled by the scheduler, and what changed with time.Timer in Go 1.23?
- Q179When are the arguments of a go statement evaluated? What does this print?
- Q180What does the Go memory model guarantee about starting and ending a goroutine?
- Q181A CPU profile shows lots of time in runtime.findRunnable, stealWork, futex and usleep. What is going on and how do you fix it?
- Q182How does testing/synctest (Go 1.25) make concurrent, time-dependent code testable?