互聯網時代以降,因爲在線用戶數量的爆炸,單臺服務器處理的鏈接也水漲船高,迫使編程模式由從前的串行模式升級到併發模型,而幾十年來,併發模型也是一代代地升級,有IO多路複用、多進程以及多線程,這幾種模型都各有長短,現代複雜的高併發架構大可能是幾種模型協同使用,不一樣場景應用不一樣模型,揚長避短,發揮服務器的最大性能,而多線程,由於其輕量和易用,成爲併發編程中使用頻率最高的併發模型,然後衍生的協程等其餘子產品,也都基於它,而咱們今天要分析的 goroutine 也是基於線程,所以,咱們先來聊聊線程的三大模型:java
線程的實現模型主要有3種:內核級線程模型、用戶級線程模型和兩級線程模型(也稱混合型線程模型),它們之間最大的差別就在於用戶線程與內核調度實體(KSE,Kernel Scheduling Entity)之間的對應關係上。而所謂的內核調度實體 KSE 就是指能夠被操做系統內核調度器調度的對象實體,簡單來講, KSE 就是內核級線程,是操做系統內核的最小調度單元,也就是咱們寫代碼的時候通俗理解上的線程了。python
用戶線程與內核線程KSE是多對一(N : 1)的映射模型,多個用戶線程的通常從屬於單個進程而且多線程的調度是由用戶本身的線程庫來完成,線程的建立、銷燬以及多線程之間的協調等操做都是由用戶本身的線程庫來負責而無須藉助系統調用來實現。一個進程中全部建立的線程都只和同一個KSE在運行時動態綁定,也就是說,操做系統只知道用戶進程而對其中的線程是無感知的,內核的全部調度都是基於用戶進程。許多語言實現的 協程庫 基本上都屬於這種方式(好比python的gevent)。因爲線程調度是在用戶層面完成的,也就是相較於內核調度不須要讓CPU在用戶態和內核態之間切換,這種實現方式相比內核級線程能夠作的很輕量級,對系統資源的消耗會小不少,所以能夠建立的線程數量與上下文切換所花費的代價也會小得多。但該模型有個原罪:並不能作到真正意義上的併發,假設在某個用戶進程上的某個用戶線程由於一個阻塞調用(好比I/O阻塞)而被CPU給中斷(搶佔式調度)了,那麼該進程內的全部線程都被阻塞(由於單個用戶進程內的線程自調度是沒有CPU時鐘中斷的,從而沒有輪轉調度),整個進程被掛起。即使是多CPU的機器,也無濟於事,由於在用戶級線程模型下,一個CPU關聯運行的是整個用戶進程,進程內的子線程綁定到CPU執行是由用戶進程調度的,內部線程對CPU是不可見的,此時能夠理解爲CPU的調度單位是用戶進程。因此不少的協程庫會把本身一些阻塞的操做從新封裝爲徹底的非阻塞形式,而後在之前要阻塞的點上,主動讓出本身,並經過某種方式通知或喚醒其餘待執行的用戶線程在該KSE上運行,從而避免了內核調度器因爲KSE阻塞而作上下文切換,這樣整個進程也不會被阻塞了。golang
用戶線程與內核線程KSE是一對一(1 : 1)的映射模型,也就是每個用戶線程綁定一個實際的內核線程,而線程的調度則徹底交付給操做系統內核去作,應用程序對線程的建立、終止以及同步都基於內核提供的系統調用來完成,大部分編程語言的線程庫(好比Java的java.lang.Thread、C++11的std::thread等等)都是對操做系統的線程(內核級線程)的一層封裝,建立出來的每一個線程與一個獨立的KSE靜態綁定,所以其調度徹底由操做系統內核調度器去作。這種模型的優點和劣勢一樣明顯:優點是實現簡單,直接藉助操做系統內核的線程以及調度器,因此CPU能夠快速切換調度線程,因而多個線程能夠同時運行,所以相較於用戶級線程模型它真正作到了並行處理;但它的劣勢是,因爲直接藉助了操做系統內核來建立、銷燬和以及多個線程之間的上下文切換和調度,所以資源成本大幅上漲,且對性能影響很大。編程
兩級線程模型是博採衆長以後的產物,充分吸取前兩種線程模型的優勢且儘可能規避它們的缺點。在此模型下,用戶線程與內核KSE是多對多(N : M)的映射模型:首先,區別於用戶級線程模型,兩級線程模型中的一個進程能夠與多個內核線程KSE關聯,因而進程內的多個線程能夠綁定不一樣的KSE,這點和內核級線程模型類似;其次,又區別於內核級線程模型,它的進程裏的全部線程並不與KSE一一綁定,而是能夠動態綁定同一個KSE, 當某個KSE由於其綁定的線程的阻塞操做被內核調度出CPU時,其關聯的進程中其他用戶線程能夠從新與其餘KSE綁定運行。因此,兩級線程模型既不是用戶級線程模型那種徹底靠本身調度的也不是內核級線程模型徹底靠操做系統調度的,而是中間態(自身調度與系統調度協同工做),也就是 — 『薛定諤的模型』(誤),由於這種模型的高度複雜性,操做系統內核開發者通常不會使用,因此更多時候是做爲第三方庫的形式出現,而Go語言中的runtime調度器就是採用的這種實現方案,實現了Goroutine與KSE之間的動態關聯,不過Go語言的實現更加高級和優雅;該模型爲什麼被稱爲兩級?即用戶調度器實現用戶線程到KSE的『調度』,內核調度器實現KSE到CPU上的『調度』。服務器
Go語言基於併發(並行)編程給出的自家的解決方案。goroutine是什麼?一般goroutine會被當作coroutine(協程)的 golang實現,從比較粗淺的層面來看,這種認知也算是合理,但實際上,goroutine並不是傳統意義上的協程,如今主流的線程模型分三種:內核級線程模型、用戶級線程模型和兩級線程模型(也稱混合型線程模型),傳統的協程庫屬於用戶級線程模型,而goroutine和它的Go Scheduler
在底層實現上實際上是屬於兩級線程模型,所以,有時候爲了方便理解能夠簡單把goroutine類比成協程,但內心必定要有個清晰的認知 — goroutine並不等同於協程。多線程
REFERENCE:架構