自己對go協程的理解

前言:

下面是自己看了很多資料后總結下來的不一定對.因為能力還不夠,還不能夠深入源碼一探究竟.
如果自己哪里不對,希望可以留言.一起討論一下.

進程線程協程的理解

1. 進程 :是計算機進行資源分配的最小單位,上下文開銷很大,進程之間數據是隔離的.
2. 線程 :是計算機調度的最小單位,是計算機運行的基本單元.(個人理解 可以把線程理解成是共享堆的進程),上
下文調度相對于進程來說要輕量(因為線程之間調度不需要切換進程資源)
3. 協程 :是用戶態的一種輕量的線程,協程的調度完全由用戶控制. 不需要內核參與,所以速度很快.

go協程模型和特點

協作顧名思義是互相協作的,但是go的協程不完全是互相協作的,還會相互搶占.go底層是基于gmp模型實現的協程調度.
M: 系統真正的線程.m的任務就是想辦法去找到可以運行的g.并且運行它
P: G的集合,有個g的鏈表.一般會和m綁定.m不斷運行p上面的g.并且p會存儲m上線程的相關信息
G: go的協程真正的實體.代表正在要執行的協程的邏輯和其要運行的上下文.

自己對gmp模型的理解(不一定準確)

大家有沒有覺得m->p->g的關系其實就是 計算機->進程->線程的關系. 因為計算機涉及到很多因素的影響,所以對進程
線程之間的調度開銷會很大所以go的作者很有創造性的把這個過程在用戶態自己模擬一套.正因為如此一些簡單的調度都
不需要用到系統來參與,go表示我自己就可以完成了,所以性能要好的很多!

具體GMP是怎么運行的

首先協程實現的功能和線程是一樣的,就是讓我們能夠同時運行多個邏輯的能力(這里指的是并發不是并行).

1. 啟動協程: go程序內部都維護了一個 gfree的一個list.每次我們啟動一個新的協程的時候 程序不會 
馬上就new一個g對象.而是先去gfree里找,如果gfree里面有現成的g對象,就會直接拿來用.如果gfree里面
沒有才會去new一個對象,所有的g運行完了都不會直接銷毀.而是放到了gfree里.相當于gfree是一個g的對象池.
總而言之go底層g是復用的.

2. 協程綁定p: 當協程被啟動后就會優先依附于啟動自己的m所綁定的p上,這樣很大程度上避免了m與m之間的競爭.
當m上的p的g已經超過了承受范圍就會放到全局的g的隊列里.與m綁定的p最多只有256個g. 

3. m的運行: m就是不斷地運行依附于自己上的p上的g.為了讓全局的g隊列里也能夠被調度到.m每運行61個本地g就會
去全局g隊列里拿一個g運行.當m本地的p沒有g可以運行的時候也會去全局隊列里面拿g運行.當全局隊列也沒有g的時候.
就會去別的m綁定的p上偷一半g來運行.如果這個時候m還是拿不到g.那么這個m就會陷入睡眠.值得注意的是睡眠m也是
不會被銷毀的.防止m被頻繁的創建銷毀.

go的協程 什么時候調度的,怎么調度的

首先我們需要知道go的協作在什么情況會被調度(就是別的協程搶占)

1. 當協程運行時間過長的時候,那么這個協程在調用非內聯函數的時候會被調度:
正式因為有這個特性,go的協程才具有了搶占性,才讓goroutine不再是單純的協程. 首先程序會在啟動的時候運行一個
sysmon線程,這個線程負責整個的調度具有上帝視角.sysmon會給所有的p都記錄一個已運行的g的數量.每當p運行一個g后
都會加1.如果這個變量沒有加1,就說明這個p一直在運行同一個g任務.如果超過了10ms,就給這個g的棧信息上加個標記.
當有了這個標記,這個g在遇到非內聯函數的時候就會把自己中斷掉,移到當前p的隊列尾.

2. 當協程陷入一個系統調用.沒有返回的時候會被調度
當m進行系統調用的時候,這個m會被陷入到內核態,毫無疑問的被阻塞了.還是sysmon會定期檢測,當一個p上的g執行時間過長
且這個g還是在進行系統調用.那么sysmon會把p與當前m解綁, 然后喚醒一個之前沒事做的睡眠的m進行綁定運行.如果沒有
睡眠的m就會重新創建一個m來綁定,之前的m運行著進行系統調用的g返回后就會嘗試找沒有m的p.如果沒有找到,m就會把g放到
全局隊列,然后自己進入睡眠.

3. 當協程被channel阻塞時會被調度
這個不需要多說.當g被channel阻塞后,這個g會被放到相應的wait隊列.該g的狀態由_Gruning變成_Gwating,g會被別的g的
channel操作喚醒,那么這個g就會嘗試加入那個g所在的p的runnext,如果沒有成功,就會加入本地p隊列,全局隊列.等待被m運行.

4. 當協程進行網絡io阻塞的時候會被調度
網絡io雖然也算是系統調用,但是go原生的網絡包底層全是由epoll類似的驅動的,并不走之前系統調用那一套.sysmon線程會進行
epoll_wait,然后對程序每個socket進行標記,哪些socket是可寫的,哪些socket是可讀的,然后嘗試喚醒因為socket不可寫或者
不可讀的g,而g在對socket進行讀寫的時候就判斷sysmon有沒有對其標記過,如果沒有標記過就進入wait狀態,如果標記過了就說明可
用就繼續操作,操作完了,再把這個標記去除了,等待下次標記.

總結:

1. GMP 三者能夠隨意綁定解綁,達到了靈活調度的目的
2. go后臺運行這sysmon線程來進行調度.和上帝一樣.
3. go程序底層有g的對象池,已經對g進行了復用不會被銷毀. 同時m也是不會被銷毀的.所以從某種意義來講go確實越運行越占內存.

參考資料

1. https://juejin.im/post/5b7678f451882533110e8948
2. https://wudaijun.com/2018/01/go-scheduler/
3. https://www.w3cschool.cn/go_internals/go_internals-8h5b282y.html
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 我是在深入學習 kotlin 時第一次看到協程,作為傳統線程模型的進化版,雖說協程這個概念幾十年前就有了,但是協程...
    前行的烏龜閱讀 100,030評論 32 182
  • 進程時代 后來,現代化的計算機有了操作系統,每個程序都是一個進程,但是操作系統在一段時間只能運行一個進程,直到這個...
    大學渣PG閱讀 1,941評論 0 1
  • 如今,微信擁有月活躍用戶8億。 不可否認,當今的微信后臺擁有著強大的并發能力。 不過, 正如羅馬并非一日建成;微信...
    一凡_44e0閱讀 1,067評論 0 0
  • 進程、線程和協程 進程的定義: 進程,是計算機中已運行程序的實體。程序本身只是指令、數據及其組織形式的描述,進程才...
    星丶雲閱讀 1,442評論 2 14
  • 閱讀1小時,總計552小時,第517日。 閱讀《法律的經濟分析》至95% 即美國憲法嚴禁政府在沒有正當法律程序的條...
    龍套哥薩克海龍閱讀 173評論 0 0