What does the Go memory model guarantee about starting and ending a goroutine?
Question 180HardGo 1.22 to 1.25
Two rules matter:
- The
gostatement that starts a goroutine is synchronized before the start of that goroutine. Writes the parent made beforegoare visible to the child. - The exit of a goroutine is not synchronized with anything. Nothing the child writes is guaranteed visible to anyone unless you add synchronization (channel,
WaitGroup, mutex, atomics).
var a string
func main() {
a = "hello"
go func() { fmt.Println(a) }() // guaranteed to see "hello" (if it runs)
var b string
go func() { b = "hi" }()
fmt.Println(b) // racy: may print an empty string
}
The fix for the second case is to publish through a synchronizing operation. For example, create done := make(chan struct{}), call close(done) in the child after the write, and do <-done in the parent before the read. wg.Wait() gives the same guarantee: every Done is synchronized before the Wait it unblocks.
What the interviewer wants to hear: "it worked when I ran it" proves nothing. Sleeps are not synchronization, and busy-waiting on a plain bool can loop forever because the compiler may keep the value in a register. Use atomic.Bool or a channel.
More on Goroutines & the Scheduler
- Q178What are the scheduling implications of runtime.Goexit, blocking in init, and unbuffered sends from many goroutines?
- Q179When are the arguments of a go statement evaluated? What does this print?
- 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?