In chapter 8 of The Go Programming Language, there is a description to the concurrency echo server as below:
The arguments to the function started by go a
How go
keyword works in Go, see
Go_statements:
The function value and parameters are evaluated as usual in the calling goroutine, but unlike with a regular call, program execution does not wait for the invoked function to complete. Instead, the function begins executing independently in a new goroutine. When the function terminates, its goroutine also terminates. If the function has any return values, they are discarded when the function completes.
The function value and parameters are evaluated in place with the go
keyword (same for the defer
keyword see an example for defer keyword).
To understand the evaluation order, let's try this:
go have()(fun("with Go."))
Let's run this and read the code comments for the evaluation order:
package main
import (
"fmt"
"sync"
)
func main() {
go have()(fun("with Go."))
fmt.Print("some ") // evaluation order: ~ 3
wg.Wait()
}
func have() func(string) {
fmt.Print("Go ") // evaluation order: 1
return funWithGo
}
func fun(msg string) string {
fmt.Print("have ") // evaluation order: 2
return msg
}
func funWithGo(msg string) {
fmt.Println("fun", msg) // evaluation order: 4
wg.Done()
}
func init() {
wg.Add(1)
}
var wg sync.WaitGroup
Output:
Go have some fun with Go.
Explanation go have()(fun("with Go."))
:
First in place evaluation takes place here:
go have()(...)
first have()
part runs and the result is fmt.Print("Go ")
and return funWithGo
, then fun("with Go.")
runs, and the result is fmt.Print("have ")
and return "with Go."
; now we have go funWithGo("with Go.")
.
So the final goroutine call is go funWithGo("with Go.")
This is a call to start a new goroutine so really we don't know when it will run. So there is a chance for the next line to run: fmt.Print("some ")
, then we wait here wg.Wait()
. Now the goroutine runs this funWithGo("with Go.")
and the result is fmt.Println("fun", "with Go.")
then wg.Done()
; that is all.
Let's rewrite the above code, just replace named functions with anonymous one, so this code is same as above:
For example see:
func have() func(string) {
fmt.Print("Go ") // evaluation order: 1
return funWithGo
}
And cut this code select the have
part in the go have()
and paste then select the have
part in func have()
and press Delete
on the keyboard, then you'll have this:
This is even more beautiful, with the same result, just replace all functions with anonymous functions:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() func(string) {
fmt.Print("Go ") // evaluation order: 1
return func(msg string) {
fmt.Println("fun", msg) // evaluation order: 4
wg.Done()
}
}()(func(msg string) string {
fmt.Print("have ") // evaluation order: 2
return msg
}("with Go."))
fmt.Print("some ") // evaluation order: ~ 3
wg.Wait()
}
Let me explain it with a simple example:
1. Consider this simple code:
i := 1
go fmt.Println(i) // 1
This is clear enough: the output is 1
.
But if the Go designers decided to evaluate the function argument at the function run-time nobody knows the value of i
; you might change the i
in your code (see the next example)
i := 1
go func() {
time.Sleep(1 * time.Second)
fmt.Println(i) // ?
}()
The output is really unknown, and if the main
goroutine exits sooner, it even won't have a chance to run: Wake up and print the i
, which is i
itself may change to that specific moment.
i := 1
go func(i int) {
fmt.Printf("Step 3 i is: %d\n", i) // i = 1
}(i)
This anonymous function argument is of type int
and it is a value type, and the value of i
is known, and the compiler-generated code pushes the value 1
(i
) to the stack, so this function, will use the value 1
, when the time comes (A time in the future).
package main
import (
"fmt"
"sync"
"time"
)
func main() {
i := 1
go fmt.Println(i) // 1 (when = unknown)
go fmt.Println(2) // 2 (when = unknown)
go func() { // closure
time.Sleep(1 * time.Second)
fmt.Println(" This won't have a chance to run", i) // i = unknown (when = unknown)
}()
i = 3
wg := new(sync.WaitGroup)
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("Step 3 i is: %d\n", i) // i = 3 (when = unknown)
}(i)
i = 4
go func(step int) { // closure
fmt.Println(step, i) // i=? (when = unknown)
}(5)
i = 5
fmt.Println(i) // i=5
wg.Wait()
}
Output:
5
5 5
2
1
Step 3 i is: 3
The Go Playground output:
5
5 5
1
2
Step 3 i is: 3
As you may be noticed, the order of 1
and 2
is random, and your output may differ (See the code comments).