- 默認狀況下,全部的goroutine都在同一個原生線程裏跑,也就是隻使用了一個CPU核。可是,經過runtime.GOMAXPROCS(4)設定,能夠將goroutine調度到多個CPU上運行。
- 在同一個原生線程裏,若當前goroutine不發生阻塞,那麼不會主動讓出CPU給其餘同一線程的goroutine的。在go程序啓動時,會首先建立一個特殊的內核線程sysmom,負責監控和調度。
三類對象:
- M表明線程
- P表明處理器,每個運行的M(線程)都必須綁定一個P(處理器)
- G表明goroutine,每次使用go關鍵字的時候,都會建立一個G對象
圖解:函數
當前有兩個P,各自綁定了一個M,每一個P上掛了一個本地goroutine隊列,也有一個全局goroutine隊列。流程:線程
- 每次使用go關鍵字聲明時,一個G對象被建立並加入到本地G隊列或者全局G隊列。
- 檢查是否有空閒的P(處理器),如有那麼建立一個M(如有正在sleep的M那麼直接喚醒它)與其綁定,而後這個M循環執行goroutine任務。
- G任務執行的順序是,先從本地隊列中找。但若某個M(線程)發現本地隊列爲空,那麼會從全局隊列中截取goroutine來執行(一次性轉移(全局隊列的G個數/P個數))。若是全局隊列也空,那麼會隨機從別的P那裏截取【一半】的goroutine過來(偷竊任務),若全部的P的隊列都爲空,那麼該M(線程)就會陷入sleep。
三種調度點
若是一個goroutine運行到一個「調度點」,上下文便從隊列中取出一個goroutine,開始運行新的goroutine,下面是三種調度點:對象
- 調用runtime.gosched函數。goroutine主動放棄CPU,該goroutine會被置爲runnable狀態,而後放入全局G隊列,P繼續執行下一個goroutine。主動行爲,使用場景:通常發生在執行長任務又想其餘goroutine獲得執行機會時調用。
- 調用runtime.park函數。goroutine進入wait狀態,除非對其調用runtime.ready函數,不然該goroutine永遠不劊獲得執行。而P繼續執行下一個G任務。使用場景:讀寫channel。通常是在某個條件若是得不到知足就不能繼續運行下去時調用,當條件知足後須要使用runtime.ready喚醒它,相似於Java裏的await和notify
- 慢系統調用。將該處理器P上設置爲syscall狀態,而後對應的線程M進入系統調用阻塞等待。sysmom監控線程會按期掃描全部的P(處理器),若發現一個P處於syscall狀態,就講=將M(線程)和P(處理器)分離,並再分配一個M與這個P綁定,從而繼續執行P本地隊列中的其餘goroutine。當以前阻塞的M從系統調用返回後,將其執行的goroutine放入全局G隊列中,該M去sleep。