goroutine上下文切換機制

  goroutine是go語言的協程,go語言在語言和編譯器層面提供對協程的支持。goroutine跟線程一個很大區別就是線程是操做系統的對象,而goroutine是應用層實現的線程。goroutine其實是運行在線程池上的,由go的runtime實現調度,goroutine調度時,因爲不須要像線程同樣涉及到系統調用,要進行用戶態和內核態的切換,所以,goroutine被稱爲輕量級的線程,開銷要比線程小不少。然而,這裏我想到了一個問題,線程是由操做系統進行調度的,操做系統有對處理器的調度權限,所以線程在上下文切換時,操做系統能夠從正在佔用處理器的線程手中剝奪處理器的使用權,然而goroutine該怎麼完成這個操做呢?性能

  然而goroutine並不能像線程的調度那樣,goroutine調度時,必須由當前正在佔用CPU的goroutine主動讓出CPU給新的goroutine,才能完成切換操做。spa

  具體實現是這樣的,go對全部的系統調用進行了封裝,當前執行的goroutine若是正在執行系統調用或者可能會致使當前goroutine阻塞的操做時,runtime就會把當前這個goroutine切換掉。所以一個頗有意思的事情就發生了,若是當前的goroutine沒有出現上述的可能會致使goroutine切換的條件時,就能夠一直佔用CPU(實際上只是一直佔用線程),並且並不會由於這個goroutine佔用時間太長而進行切換。咱們能夠經過以下這段代碼進行驗證:操作系統

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "sync"
 6 )
 7 
 8 func process(id int) {
 9     fmt.Printf("id: %d\n", id)
10     for {
11     }
12 }
13 func main() {
14     var wg sync.WaitGroup
15     n := 10
16     wg.Add(n)
17     for i := 0; i < n; i++ {
18         go process(i)
19     }
20     wg.Wait()
21 }

這段代碼輸出以下:線程

id: 9
id: 5
id: 6
id: 0code

  按照正常的邏輯,這段代碼應該會輸出0到9一共十個id,然而執行後發現,只輸出了四個(GOMAXPROCS: goroutine底層線程池最大線程數,默認爲硬件線程數)id,這就說明實際只有四個goroutine獲得了CPU,並且沒有進行切換,由於process這個方法裏面沒有會致使goroutine切換的條件。而後咱們在for循環裏面加入一個操做,例如time.Sleep()或者make分配內存等等協程

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "sync"
 6     "time"
 7 )
 8 
 9 func process(id int) {
10     fmt.Printf("id: %d\n", id)
11     for {
12         time.Sleep(time.Second)
13     }
14 }
15 func main() {
16     var wg sync.WaitGroup
17     n := 10
18     wg.Add(n)
19     for i := 0; i < n; i++ {
20         go process(i)
21     }
22     wg.Wait()
23 }

Output:對象

id: 2
id: 0
id: 1
id: 9
id: 6
id: 3
id: 7
id: 8
id: 5
id: 4blog

  能夠看到此次的輸出就是咱們預料的結果了。在知道goroutine的調度策略以後,能夠想到這種策略可能會帶來的問題,假若有n個goroutine出現阻塞,而且n >= GOMAXPROCS時,將會致使整個程序阻塞。內存

  然而這個問題是沒法從根本上解決的,因此go給咱們提供了一個方法runtime.Gosched(),調用這個方法可讓當前的goroutine主動讓出CPU,這也不失爲一個彌補的好方法了。並且在對go程序性能調優的時候,咱們能夠根據實際狀況來調整GOMAXPROCS的值,例如當有密集的IO操做時,儘可能把這個值設置大一點,能夠避免因爲大量IO操做致使阻塞線程。編譯器

 

以上內容純屬原創,若有問題歡迎指正!

相關文章
相關標籤/搜索