二十二、Go基礎程式設計:併發程式設計—goroutine
1 goroutine是什麼
goroutine是Go並行設計的核心。goroutine說到底其實就是協程,但是它比執行緒更小,十幾個goroutine可能體現在底層就是五六個執行緒,Go語言內部幫你實現了這些goroutine之間的記憶體共享。執行goroutine只需極少的棧記憶體(大概是4~5KB),當然會根據相應的資料伸縮。也正因為如此,可同時執行成千上萬個併發任務。goroutine比thread更易用、更高效、更輕便。
2 建立goroutine
只需在函式調⽤語句前新增 go 關鍵字,就可建立併發執⾏單元。開發⼈員無需瞭解任何執⾏細節,排程器會自動將其安排到合適的系統執行緒上執行。
在併發程式設計裡,我們通常想講一個過程切分成幾塊,然後讓每個goroutine各自負責一塊工作。當一個程式啟動時,其主函式即在一個單獨的goroutine中執行,我們叫它main goroutine。新的goroutine會用go語句來建立。
示例程式碼:
package main import ( "fmt" "time" ) func newTask() { i := 0 for { i++ fmt.Printf("new goroutine: i = %d\n", i) time.Sleep(1 * time.Second) //延時1s } } func main() { //建立一個 goroutine,啟動另外一個任務 go newTask() i := 0 //main goroutine 迴圈列印 for { i++ fmt.Printf("main goroutine: i = %d\n", i) time.Sleep(1 * time.Second) //延時1s } }
程式執行結果:
3 主goroutine先退出
主goroutine退出後,其它的工作goroutine也會自動退出:
func newTask() { i := 0 for { i++ fmt.Printf("new goroutine: i = %d\n", i) time.Sleep(1 * time.Second) //延時1s } } func main() { //建立一個 goroutine,啟動另外一個任務 go newTask() fmt.Println("main goroutine exit") }
程式執行結果:
4 runtime包
4.1 Gosched
runtime.Gosched() 用於讓出CPU時間片,讓出當前goroutine的執行許可權,排程器安排其他等待的任務執行,並在下次某個時候從該位置恢復執行。
這就像跑接力賽,A跑了一會碰到程式碼runtime.Gosched() 就把接力棒交給B了,A歇著了,B繼續跑。
示例程式碼:
func main() {
//建立一個goroutine
go func(s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}("world")
for i := 0; i < 2; i++ {
runtime.Gosched() //import "runtime"
/*
遮蔽runtime.Gosched()執行結果如下:
hello
hello
沒有runtime.Gosched()執行結果如下:
world
world
hello
hello
*/
fmt.Println("hello")
}
}
4.2 Goexit
呼叫 runtime.Goexit() 將立即終止當前 goroutine 執⾏,排程器確保所有已註冊 defer 延遲呼叫被執行。
示例程式碼:
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
runtime.Goexit() // 終止當前 goroutine, import "runtime"
fmt.Println("B") // 不會執行
}()
fmt.Println("A") // 不會執行
}() //別忘了()
//死迴圈,目的不讓主goroutine結束
for {
}
}
程式執行結果:
4.3 GOMAXPROCS
呼叫 runtime.GOMAXPROCS() 用來設定可以平行計算的CPU核數的最大值,並返回之前的值。
示例程式碼:
func main() {
//n := runtime.GOMAXPROCS(1) //列印結果:111111111111111111110000000000000000000011111...
n := runtime.GOMAXPROCS(2) //列印結果:010101010101010101011001100101011010010100110...
fmt.Printf("n = %d\n", n)
for {
go fmt.Print(0)
fmt.Print(1)
}
}
在第一次執行(runtime.GOMAXPROCS(1))時,最多同時只能有一個goroutine被執行。所以 會列印很多1。過了一段時間後,GO排程器會將其置為休眠,並喚醒另一個goroutine,這時候就開始列印很多0了,在列印的時候,goroutine是被排程到作業系統執行緒上的。
在第二次執行(runtime.GOMAXPROCS(2))時,我們使用了兩個CPU,所以兩個goroutine可以一起被執行,以同樣的頻率交替列印0和1。