When are the arguments of a go statement evaluated? What does this print?
Question 179HardGo 1.22 to 1.25
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
x := 1
wg.Add(2)
go func(v int) {
defer wg.Done()
fmt.Println("arg:", v)
}(x)
go func() {
defer wg.Done()
fmt.Println("closure:", x)
}()
x = 2
wg.Wait()
}
arg: 1 is guaranteed. The function value and its arguments in a go statement are evaluated in the calling goroutine, at the go statement, just like defer. Only the call itself runs in the new goroutine.
The closure: line can print 1 or 2, and it is a data race. The closure captures the variable x itself, and main writes it with no synchronization while the goroutine reads it. go run -race reports it. The two lines can also appear in either order.
Gotcha: go s.method(expensive()) runs expensive() synchronously in the caller, and the receiver s is evaluated (copied, for a value receiver) at that point too. Wrap the call in a closure if you want the work to happen in the new goroutine.
More on Goroutines & the Scheduler
- Q177How are timers handled by the scheduler, and what changed with time.Timer in Go 1.23?
- Q178What are the scheduling implications of runtime.Goexit, blocking in init, and unbuffered sends from many goroutines?
- 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?