標籤:

關於go語言協程調度的一個問題(具體請看問題描述)?

首先看代碼:

package main

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

func main() {
runtime.GOMAXPROCS(1)
for i := 0; i &< 2; i++ { go func(index int) { for { if index == 1 { fmt.Println(index) } } }(i) } time.Sleep(time.Second) fmt.Println("end") }

在設置 MAXPROCS 為 1 的情況下, 上面的代碼既不會輸出 index 的值 1, 也不會輸出 end.

我簡單的總結了一下, 如果 goroutinue 中得代碼完全是計算密集的話, 它會一直佔用CPU (代碼中 i = 0 的情況下完全佔用了CPU), 這就會導致其他的 goroutinue 還有主函數 "餓死", 當然可以使用 runtime.Gosched 交出CPU, 也就是說要自己手動調度.

那麼如果自己手動調度的話, 不是增加了程序員的心智負擔嗎?

所以我想問一下這樣設計是不是最合理, 對程序員最友好的, 如果設計成CPU自動調度的會不會更好?


goroutine的調度是非搶佔式的。

在這個前提下,初期版本的go的調度器只能在進行系統調用的時候切換goroutine。後來1.2開始可以在調用非inline函數的時候切,實現的原理其實就是在調用函數前加一些runtime的邏輯來判斷是否要切換。這會導致需要執行的指令變多,從而略微影響性能。因此切換goroutine的時機是在性能和程序員方便程度之間做折衷。


這是調度器的bug,並不是設計出來的,而是實現的缺陷。

會引發這個bug的,只有死循環里什麼都不做的代碼,這樣的代碼是沒有實際用途的,所以這個bug的影響很小。開發時是不需要考慮這個的,沒什麼心智負擔。

現在GOMAXPROCS的默認值是cpu核數,會碰到的機會就更少了。


Goroutine 是一種協作式多任務,而不是搶佔式多任務。

一個Goroutine如果一直在計算,比如死循環,那麼就會一直佔用當前線程,直到它結束或者主動放棄。

但是Go系統里有很多個線程,部分被佔用還會新開線程,不會讓其它 Goroutine餓死。

你把GOMAXPROCS限制成 1 是你自己的問題,Go只是語言和運行庫,它不是OS,不能強制Goroutine放棄CPU。

即使你用普通C++寫普通多線程,如果你把某個線程的優先順序設置成實時,如果系統只有一顆CPU核的話,其它線程也會統統餓死。


什麼情況下你需要這麼寫?什麼情況下你需要這麼寫?什麼情況下你需要這麼寫?!


Go 語言的調度器目前還存在一定的問題,MAXPROCS 為 1 時對於高密集的 CPU 計算,什麼都不做的死循環都不能進行 goroutine 的切換,這種情形下就算你把 MAXPROCS 設置為大於 1 的值也有很大的幾率不能進行切換。也就是 Go 語言寫一些軟實時系統時還是需要注意的。

為什麼 goroutine 不能在這些情形下切換呢,簡單來說就是 Go 的 Goroutine 與 RUNTIME 都是 Native 執行的,其在執行上的地位是平等的,RUNTIME 沒有能力切換一個執行中的 Goroutine,除非其自己調出或調用RUNTIME 功在 ,因而底層只能實現協作調度。

參考資料

Erlang Go 的並發調度淺析

The Go scheduler


這麼做,減少了調度器的實現難度,操作系統本身是不支持routine的,如果routine要支持自動切換,那麼需要做一個routine的管理器,然後必須先運行管理器,然後依據管理器中的優先順序切換routine,這樣的實現上會損失一些性能,估計與go的目標不符合,而題主那樣的問題通常不會出現,也容易發現。可以說chan就是一個手動切換的過程。


我一直堅信的一點是:

程序員做的越多,機器需要做的就越少。

所以,在經濟成本可接受的範圍內,我寧願程序員多做一些,機器少做一些邏輯,程序跑得快一點。

心智不夠用的話,就乖乖掏錢,讓心智夠用的人來做。


推薦閱讀:

Gevent的協程,能夠非同步是為什麼呢?

TAG:Go語言 | 協程 |