有這樣一段 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.go
和 Dockerfile
,main.go
就是上面貼的代碼,Dockerfile
內容以下:異步
ARG GO_VERSION FROM golang:${GO_VERSION} COPY ./main.go /app/ CMD ["go", "run", "/app/main.go"]
咱們經過構建參數來指定基礎鏡像 golang 的版本(1.13
和 1.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
則能夠。協程
關於新特性搶佔式調度更詳盡的解讀,請參考以下連接:隊列