SoFunction
Updated on 2025-03-05

Solve the problem of goroutine execution speed in Golang

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.