Go 1.14 新特性之 Goroutine 搶佔式調度

代碼示例

有這樣一段 Go 代碼,在程序執行之初將 P 設置到數量爲 1,有兩個 goroutine,一個是 main,一個是執行死循環的匿名函數:golang

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!")
}

咱們分析一下程序執行過程,設置 P 數量之後,執行打印 The program starts ...,以後將匿名 goroutine 加入調度隊列,執行 Sleep 操做,在 sleep 過程當中調度器會將 main goroutine 從惟一 P 中讓出,執行匿名 goroutine,而這個 goroutine 是無限循環,而且中間沒有函數調用,致使調度器沒法插手把它讓出繼續執行 main,因此程序打印完 The program starts ... 以後會一直掛着,並不會打印 I got scheduled!docker

無函數調用的死循環 goroutine 會一直佔據一個 P,GC 須要等待全部 goroutine 中止才得以執行,從而會致使 GC 延遲。若是程序中因無心出現這種死循環 goroutine 而形成 bug,很難排查。shell

Go 1.14 以前一直是上述的執行過程,協程之間的調度是非搶佔式的。Go 1.14 引入了基於系統信號的異步搶佔調度,這樣,像上面的無函數調用的死循環 goroutine 也能夠被搶佔了,從而將 main goroutine 從新調度回 P 執行,最終會打印 I got scheduled!app

驗證

咱們建立一個項目目錄,並在裏面建立兩個文件:main.goDockerfilemain.go 就是上面貼的代碼,Dockerfile 內容以下:異步

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

咱們經過構建參數來指定基礎鏡像 golang 的版本(1.131.14),而後將 main.go 拷貝進鏡像,而後執行。函數

執行構建:ui

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

對比執行:code

$ docker run -it --rm app13:latest

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

The program starts ...
I got scheduled!

執行 app13 被阻塞住,不會打印 I got scheduled!,而執行 app14 則能夠。協程

參考

關於新特性搶佔式調度更詳盡的解讀,請參考以下連接:隊列

相關文章
相關標籤/搜索