當運行一個應用程序的時候,操做系統會給這個應用程序啓動一個進程。咱們能夠將進程看做一個包含應用程序在運行中須要用到和維護的各類資源的容器。一個進程至少包含一個線程,這個線程就是主線程。操做系統會調度線程到不一樣的CPU上執行,這個CPU不必定就是進程所在的CPU。golang
Go Runtime管理調度,垃圾收集和Goroutine的運行時環境。這裏咱們只談調度器。算法
Runtime調度器經過把Goroutine綁定到操做系統線程來運行它們。Goroutine能夠看做是輕量級的線程。每一個Goroutine用G來表示,它包含了用來跟蹤棧的字段和當前狀態。併發
Runtime跟蹤每個G而且把它們綁定到P(Logical Processor)。P能夠被看做抽象資源或者上下文,操做系統線程用M來表示(OS Thread)須要獲取它以便來執行G。你能夠經過runtime.GOMAXPROCS(numLogicalProcessors)
來調整P(Logical Processors)。spa
在Go 1.1中實現了G-P-M調度模型和work stealing算法,這個模型一直沿用至今:操作系統
每個G(Goroutine)須要綁定到P(Logical Processor)才能調度執行;就像每個User-level Thread綁定到Kernel Thread才能被調度執行。線程
建立一個Goroutine並準備運行,這個Goroutine會被放到調度器的Global隊列中。而後調度器就將這些隊列中的Goroutine分配給一個P(Logical Processor),並放到這個P對應的Local隊列中。Local隊列中的Goroutine會一直等待直到本身被分配的P執行。3d
若是正在運行的Goroutine被一個系統調用阻塞,如打開一個文件。當這種狀況發生時,M2(OS Thread)和Goroutine會從P0(Logical Processor)上分離,這個M2會一直阻塞直到系統調用返回(見上圖右邊)。與此同時,這個P0就失去了用來運行的M2。因此,調度器會建立一個新的M3,並將其綁定到該P0上。以後,調度器會從Local隊列中選擇另一個Goroutine運行。一旦剛纔阻塞的系統調用執行完畢並返回,對應的Goroutine會放回到Local隊列。code
上圖解釋了併發和並行的區別
併發 - 一個咖啡機同時服務兩個隊列
並行 - 兩個咖啡機服務同時服務兩個隊列blog
若是但願讓Goroutine並行,必須使用多於一個P(Logical Processor)。當有多個P時,調度器會將Goroutine平等分配到每一個P上。這會讓Goroutine在不一樣M(OS Thread)上運行。不過要想真的實現並行的效果,用戶須要讓本身的程序運行在有多個物理處理器的機器上。隊列
參考資料。