Suddenly I thought of the execution order of goroutine in the for loop that I had not paid attention to before, so I found a few interesting places. After trying it a few times, I didn’t have the energy to dig deeper. I hope that a master golang can briefly talk about what these places are going on.
Code:
package main import "fmt" func Count(ch chan int) { ("Count doing") ch <- 1 ("Counting") } func main() { chs := make([]chan int, 100) for i := 0; i < 100; i++ { chs[i] = make(chan int) go Count(chs[i]) ("Count",i) } for i, ch := range chs { <-ch ("Counting ", i) } }
After trying a few times, I repeatedly thought about the problem of goroutine execution.
Based on the output below, what I can see is:
1. The speed of the for loop is faster than the speed of opening a goroutine in for and executing it
2. However, the speed of opening goroutine and executing the first fmt may catch up with the speed of the for loop, such as the first 12 counts and count doing
3. Key question, is the fmt executed by the second for loop actually faster than the second fmt in goroutine? ? (It's time-consuming to put the channel?)
4. When main ends, that is, when the second for loop ends, the second fmt in goroutine is not executed
Output:
Count 0 Count 1 Count 2 Count 3 Count 4 Count 5 Count 6 Count 7 Count 8 Count 9 Count 10 Count 11 Count doing Count doing Count doing Count doing Count doing Count 12 Count doing Count doing Count doing Count doing Count doing Count doing Count doing Count 13 Count 14 Count 15 Count 16 Count 17 Count 18 Count 19 Count 20 Count 21 Count doing Count doing Count doing Count 22 Count doing Count doing Count doing Count 23 Count 24 Count 25 Count 26 Count 27 Count 28 Count 29 Count 30 Count doing Count 31 Count doing Count doing Count 32 Count 33 Count 34 Count 35 Count doing Count 36 Count doing Count doing Count 37 Count 38 Count doing Count doing Count doing Count doing Count 39 Count 40 Count 41 Count 42 Count 43 Count doing Count doing Count 44 Count 45 Count 46 Count 47 Count doing Count 48 Count 49 Count doing Count doing Count 50 Count 51 Count doing Count doing Count doing Count doing Count doing Count 52 Count 53 Count doing Count doing Count doing Count doing Count 54 Count doing Count 55 Count 56 Count 57 Count 58 Count 59 Count 60 Count 61 Count 62 Count 63 Count 64 Count 65 Count doing Count doing Count doing Count 66 Count 67 Count 68 Count 69 Count doing Count 70 Count doing Count 71 Count 72 Count doing Count 73 Count doing Count doing Count 74 Count doing Count 75 Count 76 Count 77 Count doing Count doing Count doing Count doing Count 78 Count 79 Count 80 Count 81 Count 82 Count 83 Count 84 Count 85 Count 86 Count 87 Count 88 Count 89 Count 90 Count 91 Count 92 Count 93 Count 94 Count doing Count doing Count doing Count doing Count doing Count doing Count doing Count doing Count 95 Count doing Count 96 Count doing Count 97 Count 98 Count doing Count 99 Count doing Count doing Counting 0 Counting 1 Counting 2 Counting 3 Counting 4 Counting 5 Counting 6 Count doing Count doing Counting 7 Counting 8 Count doing Counting Count doing Counting 9 Counting Count doing Count doing Count doing Count doing Count doing Counting Count doing Count doing Count doing Counting Count doing Counting Count doing Counting 10 Counting 11 Counting Count doing Count doing Count doing Count doing Count doing Count doing Counting Count doing Count doing Counting Counting Count doing Count doing Count doing Count doing Counting Count doing Counting Count doing Count doing Counting 12 Counting 13 Counting 14 Counting 15 Counting 16 Counting 17 Counting 18 Counting 19 Counting 20 Counting 21 Counting 22 Counting 23 Counting 24 Counting 25 Counting 26 Counting 27 Counting 28 Counting 29 Counting 30 Counting 31 Counting 32 Counting 33 Counting 34 Counting 35 Counting 36 Counting 37 Counting 38 Counting 39 Counting 40 Counting 41 Counting 42 Counting 43 Counting 44 Counting 45 Counting 46 Counting 47 Counting 48 Counting 49 Counting 50 Counting 51 Counting 52 Counting 53 Counting 54 Counting 55 Counting 56 Counting Counting Counting Counting Counting Counting Count doing Counting Count doing Counting Counting Counting 57 Counting 58 Counting 59 Counting 60 Counting 61 Counting 62 Counting 63 Counting 64 Counting 65 Counting 66 Counting 67 Counting 68 Counting 69 Counting 70 Counting 71 Counting 72 Counting 73 Counting 74 Counting 75 Counting 76 Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting 77 Counting 78 Counting 79 Counting 80 Counting 81 Counting 82 Counting 83 Counting 84 Counting 85 Counting 86 Counting 87 Counting 88 Counting 89 Counting 90 Counting 91 Counting 92 Counting 93 Counting 94 Counting 95 Counting 96 Counting 97 Counting 98 Counting 99
Supplement: [golang] The pit of goroutine scheduling
Today I will talk about a small pit I encountered, about goroutine scheduling.
There is already a lot of online information about goroutine scheduling, so I won’t go into details here.
Let’s briefly talk about the scheduling of goroutine that I understand. goroutine is at the language level. It has a M:N relationship with kernel threads and uses a segmented stack, which is quite lightweight.
If set to 1, there will be a context G, and there will be a corresponding kernel thread M on G. There can be many goroutines on the kernel thread M. Each context will have a queue called runqueue. When opening a goroutine with the go keyword, the goroutine will be loaded into the runqueue and then used by M to execute. If there are exactly two goroutines in the queue, the goroutine executed first will be suspended (throwing to the global runqueue) because of some time-consuming operations (system calls, read and write channels, gosched actively give up, network IO) and then dispatch the subsequent goroutine.
OK, here's the point, let's take a look at the following code
func main(){ (1) (1) go func(){ defer () for i := 0;i < 20;i++ { ("hello") f, _ := ("./data") ([]byte("hello")) } }() (1) go func(){ defer () for { } }() () }
If you run this code, you will find that it will always be blocked, and hello will never be printed
OK, there are two problems here
1. Why does the goroutine in the dead loop always run first? It shouldn't be random?
2. Why does the goroutine in the dead loop block and not be suspended?
Let’s look at the second question first. Here, I was also very upset at the time, so I posted a question online, and the reply I received was that the dead loop does not belong to any of the above states that need to be suspended, so the dead loop goroutine will continue to run, imagine a high concurrency scenario. If one of the goroutines falls into the dead loop for some reason, the current OS thread that executes the goroutine will basically be executed until the program ends, which is simply a disaster. However, 1.12 will fix this minor issue. Let's wait silently for the new version to be released.
Let’s look at the first question. Why does the goroutine in the dead loop always run first? It shouldn't be random? I have tested it many times, and the second goroutine is run first. Well, even if the second goroutine is run first, it is random. This is about how golang's compiler implements randomness. Check out the boss's answer:
<It's not that it will always be like this when tested many times. The language specification does not say that it must be in this order. Then the compiler can implement it no matter how it is implemented, because it does not violate the specification.
So you have to think of it as random and cannot rely on this undetermined behavior, otherwise it is very likely that the new version of the compiler will destroy the fact that you depend on. Some projects dare not upgrade the compiler version because they rely on the behavior of a specific version of the compiler and will break as soon as they are upgraded.
If you don't test it yourself many times, you can rely on it. Different compilers, operating systems, hardware, etc. may have different results. The only language specification ( /ref/spec ) that can be relied on, and compiler implementers will definitely abide by it.
This has solved the above two problems.
Let's take a look at another version
func main(){ (1) (1) go func(){ defer () for { } }() (1) go func(){ defer () for i := 0;i < 20;i++ { ("hello") f, _ := ("./data") ([]byte("hello")) ("") ("request successful") } }() () }
The execution result is that a hello will be printed first and then fall into a dead loop. This also shows that when goroutine encounters time-consuming operations or system calls, the subsequent code will not be executed (request successful is not printed), and will be thrown into the global runqueue, and then the goroutine waiting in the runqueue will be executed.
I hope it can help the friendly forces who are learning golang like me to better understand goroutine's scheduling problem
The above is personal experience. I hope you can give you a reference and I hope you can support me more. If there are any mistakes or no complete considerations, I would like to give you advice.