要理解甚么是goroutine,我們先來看看進程、線程和協程它們之間的區分,這能幫助我們更好的理解goroutine。
進程:分配完全獨立的地址空間,具有自己獨立的堆和棧,既不同享堆,亦不同享棧,進程的切換只產生在內核態,由操作系統調度。
線程:和其它本進程的線程同享地址空間,具有自己獨立的棧和同享的堆,同享堆,不同享棧,線程的切換1般也由操作系統調度(標準線程是的)。
協程:和線程類似,同享堆,不同享棧,協程的切換1般由程序員在代碼中顯式控制。
進程和線程的切換主要依賴于時間片的控制(關于進程和線程的調度方式,具體可參看這篇文章:http://blog.chinaunix.net/uid⑵0476365-id⑴942505.html),而協程的切換則主要依賴于本身,這樣的好處是避免了無意義的調度,由此可以提高性能,但也因此,程序員必須自己承當調度的責任。
goroutine可以看做是協程的go語言實現,從百度百科上看協程的定義:與子例程1樣,協程(coroutine)也是1種程序組件。相對子例程而言,協程更加1般和靈活,但在實踐中使用沒有子例程那樣廣泛。實際上,我們可以把子例程當作是協程的1種特例。1般來講,如果沒有顯式的讓出CPU,就會1直履行當前協程。
我們知道goroutine是協程的go語言實現,它是語言原生支持的,相對1般由庫實現協程的方式,goroutine更加強大,它的調度1定程度上是由go運行時(runtime)管理。其好處之1是,當某goroutine產生阻塞時(例猶如步IO操作等),會自動出讓CPU給其它goroutine。
goroutine的使用非常簡單,例如foo是1個函數:
go foo()
就1個關鍵字go弄定了,這里會啟動1個goroutine履行foo函數,然后CPU繼續履行后面的代碼。這里雖然啟動了goroutine,但其實不意味著它會得到馬上調度,關于goroutine的調度我們稍后再探討。
goroutine是非常輕量級的,它就是1段代碼,1個函數入口,和在堆上為其分配的1個堆棧(初始大小為4K,會隨著程序的履行自動增長刪除)。所以它非常便宜,我們可以很輕松的創建上萬個goroutine。
默許的, 所有goroutine會在1個原生線程里跑,也就是只使用了1個CPU核。在同1個原生線程里,如果當前goroutine不產生阻塞,它是不會讓出CPU時間給其他同線程的goroutines的。除被系統調用阻塞的線程外,Go運行庫最多會啟動$GOMAXPROCS個線程來運行goroutine。
那末goroutine究竟是如何被調度的呢?我們從go程序啟動開始說起。在go程序啟動時會首先創建1個特殊的內核線程sysmon,從名字就能夠看出來它的職責是負責監控的,goroutine背后的調度可以說就是靠它來弄定。
接下來,我們再看看它的調度模型,go語言當前的實現是N:M。即1定數量的用戶線程映照到1定數量的OS線程上,這里的用戶線程在go中指的就是goroutine。go語言的調度模型需要弄清楚3個概念:M、P和G,以下圖表示:
M代表OS線程,G代表goroutine,P的概念比較重要,它表示履行的上下文,其數量由$GOMAXPROCS決定,1般來講正好等于處理器的數量。M必須和P綁定才能履行G,調度器需要保證所有的P都有G履行,以保證并行度。以下圖:
從圖中我們可以看見,當前有兩個P,各自綁定了1個M,并分別履行了1個goroutine,我們還可以看見每一個P上還掛了1個G的隊列,這個隊列是代表私有的任務隊列,它們實際上都是runnable狀態的goroutine。當使用go關鍵字聲明時,1個goroutine便被加入到運行隊列的尾部。1旦1個goroutine運行到1個調度點,上下文便從運行隊列中取出1個goroutine, 設置好棧和指令指針,便開始運行新的goroutine。
那末go中切換goroutine的調度點有哪些呢?具體有以下3種情況
調度點的情況說清楚了,但全部模型還其實不完全。我們知道當使用go去調用1個函數,會生成1個新的goroutine放入當前P的隊列中,那末甚么時候生成別的OS線程,各個OS線程又是如何做負載均衡的呢?
當M從隊列中拿到1個可履行的G后,首先會去檢查1下,自己的隊列中是不是還有等待的G,如果還有等待的G,并且也還有空閑的P,此時就會通知runtime分配1個新的M(如果有在睡覺的OS線程,則直接喚醒它,沒有的話則生成1個新的OS線程)來分擔負務。
如果某個M發現隊列為空以后,會首先從全局隊列中取1個G來處理。如果全局隊列也空了,則會隨機從別的P那里直接截取1半的隊列過來(偷竊任務),如果發現所有的P都沒有可供偷竊的G了,該M就會墮入沉睡。
全部調度模型大致就是這模樣了,和所有協程的調度1樣,在響應時間上,這類協作式調度是硬傷。很容易致使某個協程長時間沒法得到履行。但整體來講,它帶來的好處更加讓人驚嘆。想要了解的更多可以看看我下面列出的1些參考資料,或是直接看它的源碼:http://golang.org/src/runtime/proc.c
總綱傳送門:golang技術隨筆總綱