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操做致使阻塞線程。編譯器
以上內容純屬原創,若有問題歡迎指正!