Goroutine preemptive scheduling with new features of go 1.14

Time:2020-8-3

Code example

There is a go code that sets p to the number of1There are two goroutines and one ismain, an anonymous function that executes an endless loop

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    runtime.GOMAXPROCS(1)

    fmt.Println("The program starts ...")

    go func() {
        for {
        }
    }()

    time.Sleep(time.Second)
    fmt.Println("I got scheduled!")
}

Let’s analyze the program execution process, set the number of P, and then printThe program starts ...After that, the anonymous goroutine is added to the scheduling queue and executedSleepThe scheduler will operate in the sleep process.mainGoroutine is released from the unique p to execute anonymous goroutine. This goroutine is an infinite loop, and there is no function call in the middle, which makes the scheduler unable to intervene to release it to continue executionmain, so the program is finished printingThe program starts ...After that, it will hang all the time and will not printI got scheduled!

An endless loop goroutine without function calls will always occupy a P. GC needs to wait for all goroutines to stop before it can be executed, which will cause GC delay. If there is a bug in the program due to the unintentional occurrence of such an endless loop goroutine, it is difficult to find out.

Go 1.14 has been the execution process mentioned above, and the scheduling between CO programs is non preemptive.Go 1.14 introduces asynchronous preemption scheduling based on system signalIn this way, the loop goroutine without function call can also be preemptedmainGoroutine reschedules back to P for execution and will eventually printI got scheduled!

verification

We create a project directory and create two files in it:main.goandDockerfilemain.goIt’s the code on it,DockerfileThe contents are as follows:

ARG GO_VERSION
FROM golang:${GO_VERSION}
COPY ./main.go /app/
CMD ["go", "run", "/app/main.go"]

We specify the version of the underlying mirror golang by building parameters(1.13and1.14), and thenmain.goCopy into the mirror and execute.

Execution build:

$ docker build -t app13 --build-arg GO_VERSION=1.13 .
$ docker build -t app14 --build-arg GO_VERSION=1.14 .

Contrast execution:

$ docker run -it --rm app13:latest

The program starts ...
$ docker run -it --rm app14:latest

The program starts ...
I got scheduled!

implementapp13It’s blocked. It won’t printI got scheduled!, and executeapp14Yes.

reference resources

For a more detailed explanation of the new feature preemptive scheduling, please refer to the following link:

  • Go 1.14 Release Notes
  • Some noteworthy changes in go 1.14