儘管 Go 編譯器產生的是本地可執行代碼,這些代碼仍舊運行在 Go 的 runtime(這部分的代碼可以在 runtime 包中找到)當中。這個 runtime 類似 Java 和 .NET 語言所用到的虛擬機器,它負責管理組件括記憶體配置、記憶體回收(第 10.8 節)、棧處理、goroutine、channel、切片(slice)、map 和反射(reflection)等等。
runtime
調度器是個非常有用的東西,關於 runtime
包幾個方法:
Gosched:讓當前線程讓出 cpu
以讓其它線程運行,它不會掛起當前線程,因此當前線程未來會繼續執行
NumCPU:返回當前系統的 CPU
核心數量
GOMAXPROCS:設定最大的可同時使用的 CPU
核心數
Goexit:退出當前 goroutine
(但是defer
語句會照常執行)
NumGoroutine:返回正在執行和排隊的任務總數
GOOS:目標作業系統
NumCPU
package mainimport ( "fmt" "runtime")func main() { fmt.Println("cpus:", runtime.NumCPU()) fmt.Println("goroot:", runtime.GOROOT()) fmt.Println("archive:", runtime.GOOS)}
運行結果:
image.png
GOMAXPROCS
Golang
預設所有任務都運行在一個 cpu
核裡,如果要在 goroutine
中使用多核,可以使用 runtime.GOMAXPROCS
函數修改,當參數小於 1 時使用預設值。
package mainimport ( "fmt" "runtime")func init() { runtime.GOMAXPROCS(1)}func main() { // 任務邏輯...}
Gosched
這個函數的作用是讓當前 goroutine
讓出 CPU
,當一個 goroutine
發生阻塞,Go
會自動地把與該 goroutine
處於同一系統線程的其他 goroutine
轉移到另一個系統線程上去,以使這些 goroutine
不阻塞。
注意關閉通道使用的是close()
方法。
引申思考:close()關閉通道的時候,如果關閉一個已經關閉的通道,會報錯。那我們關閉時如何確定通道此時的狀態呢?
這裡有一篇文章大家可以參考下
https://www.jianshu.com/p/d24dfbb33781
package mainimport ( "fmt" "runtime")func init() { runtime.GOMAXPROCS(1) //使用單核}func main() { exit := make(chan int) go func() { defer close(exit) go func() { fmt.Println("b") }() }() for i := 0; i < 4; i++ { fmt.Println("a:", i) if i == 1 { runtime.Gosched() //切換任務 } } <-exit}
結果:
image.png
使用多核測試:
package mainimport ( "fmt" "runtime")func init() { runtime.GOMAXPROCS(4) //使用多核}func main() { exit := make(chan int) go func() { defer close(exit) go func() { fmt.Println("b") }() }() for i := 0; i < 4; i++ { fmt.Println("a:", i) if i == 1 { runtime.Gosched() //切換任務 } } <-exit}
結果:
image.png
根據你機器來設定運行時的核心數,但是運行結果不一定與上面相同,或者在 main
函數的最後加上 select{} 讓程式阻塞,則結果如下:
image.png
多核比較適合那種 CPU
密集型程式,如果是 IO
密集型使用多核會增加 CPU
切換的成本。
Gosched再說明
Gosched用來讓出CPU的時間片。就像是接力賽跑,A跑了一會碰到runtime.Gosched()
,就會把時間片給B,讓B接著跑。
package mainimport ( "runtime" "fmt")func init() { runtime.GOMAXPROCS(1)}func say(s string){ for i := 0; i < 2; i++ { runtime.Gosched() fmt.Println(s) }}func main() { go say("world") say("hello")}
輸出結果:
hello
world
hello
注意結果:
1、先輸出了hello,後輸出了world.
2、hello輸出了2個,world輸出了1個(因為第2個hello輸出完,主線程就退出了,第2個world沒機會了)
把代碼中的runtime.Gosched()注釋掉,執行結果是:
hello
hello
因為say("hello")這句佔用了時間,等它執行完,線程也結束了,say("world")就沒有機會了。
這裡同時可以看出,go中的goroutins並不是同時在運行。事實上,如果沒有在代碼中通過
runtime.GOMAXPROCS(n) 其中n是整數,
指定使用多核的話,goroutins都是在一個線程裡的,它們之間通過不停的讓出時間片輪流程執行,達到類似同時啟動並執行效果。