淺談協程和Go語言的Goroutine

0x00.前言

前面寫了一篇初識Go語言和你們一塊兒學習了Go語言的巨大潛力、語言簡史、殺手鐗特性等,感興趣的讀者能夠回顧一下。html

今天來學習Go語言的Goroutine機制,這也多是Go語言最爲吸引人的特性了,理解它對於掌握Go語言大有裨益,話很少說開始吧!git

經過本文你將瞭解到如下內容:golang

  • 什麼是協程以及橫向對比優點
  • Go語言的Goroutine機制底層原理和特色

0x01.聊聊協程

你們對於進程、線程二位明星都很熟悉,但協程就沒有火了,是協程不是攜程哦!面試

協程並非Go語言特有的機制,相反像Lua、Ruby、Python、Kotlin、C/C++等也都有協程的支持,區別在於有的是從語言層面支持、有的經過插件類庫支持。Go語言是原生語言層面支持,本文也是從Go角度去理解協程。算法

                

1.1 協程基本概念和提出者

協程英文是Coroutine譯爲協同程序,咱們來看下維基百科對Coroutine的介紹:
編程

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.
Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes.
According to Donald Knuth, Melvin Conway coined the term coroutine in 1958 when he applied it to construction of an assembly program.The first published explanation of the coroutine appeared later, in 1963.

簡單翻譯一下:segmentfault

協同程序是一種計算機程序組件,它容許暫停和恢復執行,從而能夠做爲通用化的非搶佔式多任務處理子程序。
協同程序很是適合實現例如協做任務、異常、事件循環、迭代器、管道等熟悉的程序組件。
根據唐納德·克努特的說法,梅爾文·康威在1958年將Coroutine這個術語應用於裝配程序的構建,直到在1963年才首次發表了闡述Coroutine的論文。

協程的提出者梅爾文·愛德華·康威是一位計算機科學家,除了協程以外他還創造了Conway's Law康威定律,他基於社會學觀察提出了系統設計的一些觀點,本文就不展開了,感興趣的能夠看下做者的論文How Do Committees Invent?:緩存

www.melconway.com/Home/Commit…

1.2 協程和進線程的對比

咱們來複習一下進線程和協程的一些基本特色吧:安全

進程是系統資源分配的最小單位, 進程包括文本段text region、數據段data region和堆棧段stack region等。進程的建立和銷燬都是系統資源級別的,所以是一種比較昂貴的操做,進程是搶佔式調度其有三個狀態:等待態、就緒態、運行態。進程之間是相互隔離的,它們各自擁有本身的系統資源, 更加安全可是也存在進程間通訊不便的問題。

進程是線程的載體容器,多個線程除了共享進程的資源還擁有本身的一少部分獨立的資源,所以相比進程而言更加輕量,進程內的多個線程間的通訊比進程容易,可是也一樣帶來了同步和互斥的問題和線程安全問題,儘管如此多線程編程仍然是當前服務端編程的主流,線程也是CPU調度的最小單位,多線程運行時就存在線程切換問題,其狀態轉移如圖:
              
協程在有的資料中稱爲微線程或者用戶態輕量級線程,協程調度不須要內核參與而是徹底由用戶態程序來決定,所以協程對於系統而言是無感知的。協程由用戶態控制就不存在搶佔式調度那樣強制的CPU控制權切換到其餘進線程,多個協程進行協做式調度,協程本身主動把控制權轉讓出去以後,其餘協程才能被執行到,這樣就避免了系統切換開銷提升了CPU的使用效率。

搶佔式調度和協做式調度的簡單對比:網絡

看到這裏咱們難免去想:看着協做式調度優勢更多,那麼爲何一直是搶佔式調度佔上風呢?讓咱們繼續一塊兒學習,可能就能解答這個問題了。

1.3 實際工做中的咱們

咱們寫程序的時常常須要考慮的因素就是提升機器使用率,這個很是好理解。固然機器使用率和開發效率維護成本每每存在權衡,說句大白話就是:要麼費人力要麼費機器,選一個吧

機器成本里面最貴的就是CPU了,程序通常分爲CPU密集型和IO密集型,對於CPU密集型咱們的優化空間可能沒那麼多,但對於IO密集型卻有很是大的優化空間,試想咱們的程序老是處於IO等待中讓CPU呼呼睡大覺,那該多糟糕。

爲了提升IO密集型程序的CPU使用率,咱們嘗試多進程/多線程編程等讓多個任務一塊兒跑分時複用搶佔式調度,這樣提升了CPU的利用率,但因爲多個進線程存在調度切換,這也有必定的資源消耗,所以進線程數量不可能無限增大。

咱們如今寫的程序大部分都是同步IO的,效率還不夠高,所以出現了一些異步IO框架,可是異步框架的編程難度比同步框架要大,但不能否認異步是一個很好的優化方向,先不要暈,來看下同步IO和異步IO就知道了:

同步是指應用程序發起I/O請求後須要等待或者輪詢內核I/O操做完成後才能繼續執行,異步是指應用程序發起I/O請求後仍繼續執行,當內核I/O操做完成後會通知應用程序或者調用應用程序註冊的回調函數。

咱們以C/C++開發的服務端程序爲例,Linux的異步IO出現的比較晚,所以像epoll之類的IO複用技術仍然有至關大的地盤,可是同步IO的效率畢竟不如異步IO,所以當前的優化方向包括:異步IO框架(像boost.asio框架)和協程方案(騰訊libco)。

         

0x02.Go和協程

咱們知道協程是Coroutine,Go語言在語言層面對協程進行了原生支持而且稱之爲Goroutine,這也是Go語言強大併發能力的重要支撐,Go的CSP併發模型是經過Goroutine和channel來實現的,後續會專門寫一下CSP併發模型。

2.1 協做式調度和調度器

協做式調度中用戶態協程會主動讓出CPU控制權來讓其餘協程使用,確實提升了CPU的使用率,可是不禁得去思考用戶態協程不夠智能怎麼辦?不知道什麼時候讓出控制權也不知道什麼時候恢復執行。

讀到這裏突然明白了搶佔式調度的優點了,在搶佔式調度中都是由系統內核來完成的,用戶態不須要參與,而且內核參與使得平臺移植好,說到底仍是各有千秋啊!

爲了解決這個問題咱們須要一箇中間層來調度這些協程,這樣才能讓用戶態的成千上萬個協程穩定有序地跑起來,咱們姑且把這個中間層稱爲用戶態協程調度器吧!

2.2 Goroutine和Go的調度器模型

Go語言從2007年末開發直到今天已經發展了12年,Go的調度器也不是一蹴而就的,在最初的幾個版本中Go的調度器也很是簡陋,沒法支撐大併發。

通過多個版本的迭代和優化,目前已經有很優異的性能了,不過咱們仍是來回顧一下Go調度器的發展歷程(詳見參考一):

                                                          圖片來自網絡

Go的調度器很是複雜,篇幅所限本文只提一些基本的概念和原理,後續會深刻去展開Go的調度器。

最近幾個版本的Go調度器採用GPM模型,其中有幾個概念先看下:

                                                          圖片來自網絡

GPM模型使用一種M:N的調度器來調度任意數量的協程運行於任意數量的系統線程中,從而保證了上下文切換的速度而且利用多核,可是增長了調度器的複雜度。

來看兩張圖來進一步理解一下:

          
                                                            圖片來自網絡
   

整個GPM調度的簡單過程以下:

新建立的Goroutine會先存放在Global全局隊列中,等待Go調度器進行調度,隨後Goroutine被分配給其中的一個邏輯處理器P,並放到這個邏輯處理器對應的Local本地運行隊列中,最終等待被邏輯處理器P執行便可。
在M與P綁定後,M會不斷從P的Local隊列中無鎖地取出G,並切換到G的堆棧執行,當P的Local隊列中沒有G時,再從Global隊列中獲取一個G,當Global隊列中也沒有待運行的G時,則嘗試從其它的P竊取部分G來執行至關於P之間的負載均衡。

Goroutine在整個生存期也存在不一樣的狀態切換,主要的有如下幾種狀態:

畫個狀態圖看下:

0x03.巨人的肩膀

draveness.me/golang/docs…

www.flysnow.org/2017/04/11/…

人類身份驗證 - SegmentFault

tiancaiamao.gitbooks.io/go-internal…

wudaijun.com/2018/01/go-…

就想叫yoko:[譯] Go語言調度器

0x04.往期精彩

0x05.關於我

相關文章
相關標籤/搜索