This is a creation in Article, where the information may have evolved or changed.
Let's look at the following interview questions:
func main() { runtime.GOMAXPROCS(1) wg := sync.WaitGroup{} wg.Add(20) for i := 0; i < 10; i++ { go func() { fmt.Println("go routine 1 i: ", i) wg.Done() }()}for i := 0; i < 10; i++ { go func(i int) { fmt.Println("go routine 2 i: ", i) wg.Done() }(i)}wg.Wait() }
Without executing the code, the brain complements what the output should look for.
When I see this problem again, the first thing I think about is that the output should be 0--9 output in turn. But after the implementation of the glasses, the wrong is not 1:30 o ' all. First look at the results of my local execution:
go routine 2 i: 9 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 2 i: 0 go routine 2 i: 1 go routine 2 i: 2 go routine 2 i: 3 go routine 2 i: 4 go routine 2 i: 5 go routine 2 i: 6 go routine 2 i: 7 go routine 2 i: 8
Not surprisingly? Not surprised?
Why this is the result, and then read the official Google Golang document, finally got some clues.
Concurrency is not equal to parallel
Golang's core developer, Rob Pike, specifically mentions this topic (interested in watching this video or reading the original PPT)
Although we used go to create a goroutine in the For loop, we thought that, of course, each time the loop variable was executed, Golang would definitely execute the goroutine and then output the variables at that time. At this point, we get into a mindset. Default concurrency equals parallelism.
Admittedly, the goroutine created by Go is the function code that executes it concurrently. But will it have to be executed every time we think about the loop? The answer is NO!
Rob Pike specifically mentions that concurrency in Golang means that some functions in the code structure can be logically run concurrently, but not physically . Parallelism refers to the use of different CPUs to perform different or identical tasks at the physical level.
Golang's goroutine scheduling model determines that each goroutine is run in a virtual CPU (that is, we pass runtime. Gomaxprocs (1) The number of virtual CPUs set). The number of virtual CPUs does not necessarily match the number of actual CPUs. Each goroutine is selected for maintenance by a specific P (virtual CPU), and m (Physical computing resources) picks a valid p each time, and then executes the goroutine in P.
Each p puts its own goroutine into a G queue, which includes goroutine stack information, executable information, and so on. By default, the number of P is equal to the actual number of physical CPUs. So when we create goroutine through loops, each goroutine is assigned to a different P queue. And the number of M is not unique, when m randomly pick p, it is also the same as randomly selected goroutine.
In the subject, we set the p=1. So all the goroutine will be bound to the same p. If we modify runtime. Gomaxprocs, you will see a different order. If we output the Goroutine ID, we can see the randomly selected effect:
func main() { wg := sync.WaitGroup{} wg.Add(20) for i := 0; i < 10; i++ { go func() { var buf [64]byte n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] id, err := strconv.Atoi(idField) if err != nil { panic(fmt.Sprintf("cannot get goroutine id: %v", err)) }fmt.Println("go routine 1 i: ", i, id) wg.Done() }()}for i := 0; i < 10; i++ { go func(i int) { var buf [64]byte n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] id, err := strconv.Atoi(idField) if err != nil { panic(fmt.Sprintf("cannot get goroutine id: %v", err)) }fmt.Println("go routine 2 i: ", i, id) wg.Done() }(i)}wg.Wait() }
The output is as follows:
Go routine 2 i:9 go routine 1 i:10 go routine 1 i:10 5 go routine 1 i:10 6 go routine 2 I:3 18 Go routine 1 I:10 9 go routine 1 i:10 go routine 1 i:10 8 go routine 2 i:0 go routine 2 i:4 go routine 2 I:6 Go routine 1 i:10 7 go routine 1 i:10 go routine 2 i:7 go routine 2 i:8 go routine 1 i:10 13 Go routine 2 i:5 go routine 1 i:10 go routine 2 i:1 go routine 2 i:2 17⋊> ~/s/g/g/s/t/c/goroutine./go Routinego routine 1 I:10 one go routine 2 i:9 go routine 1 i:10 6 go routine 1 i:10 go routine 1 i:10 9 go Routine 1 i:10 go routine 1 i:10 go routine 2 i:0 go routine 1 i:10 go routine 1 i:10 5 go routine 2 I:1 Go routine 2 i:5 go routine 1 i:10 7 go routine 2 i:7 go routine 2 i:3 go routine 2 i:2 Routine 2 i:4 go routine 1 i:10 8 go routine 2 i:8 go routine 2 i:6
We go back to this problem, although in the loop we define a goroutine through go. But we said that concurrency is not equal to parallelism. So although it is defined, it is not likely to be executed at this moment. You need to wait for m to select p before executing the goroutine. about how Goroutine is scheduled (GPM model) in Golang, refer to scalable Go Scheduler Design doc or learnconcurrency
It should then be understood why the output goroutine2 and then output goroutine1.
Let's explain why the output in Goroutine1 is 10.
Goroutine How to bind a variable
In the Golang for Loop, Golang uses the same variable instance each time (that is, the I used in the title). And Golang is a shared environment variable .
When dispatched to this goroutine, it will read the stored variable address directly, there is a problem:goroutine Save only the variable address, so the variable is possible to be modified .
The For loop in the combined problem, each time the same variable address is used, that is, I change every time, to the end of the loop, I becomes 10. And Goroutine saved only I memory address only, so when goroutine1 execution, do not hesitate to the content of I read out, how much? 10!
But why is goroutine2 not 10?
In turn, it is easy to understand the goroutine2. Because a new variable is regenerated in each loop, each goroutine holds the address of its new variable. These variables do not interfere with each other and are not tampered with by anyone. As a result, the output will be output from 0-9 sequentially.
In fact, these problems, Golang official has been issued warning prompt. Just look at the official documentation habits, so you planted the pit.
Fortunately in time to find their own shortcomings, mend, it is not too late.