Go学习笔记—并发高级
Go并發機制
? 協程:一個線程可以對應多個協程,協程串行運行在用戶空間。協程運行在線程之上,當一個協程執行完成后,可以選擇主動讓出,讓另一個協程運行在當前線程之上。協程并沒有增加線程數量,只是在線程的基礎之上通過分時復用的方式運行多個協程,而且協程的切換在用戶態完成,切換的代價比線程從用戶態到內核態的代價小很多。
? Go摒棄線程、進程、協程,提出goroutine:
- M(machine):一個M代表一個內核線程(與KSE一一對應)
- P(processor):一個P代表執行一個Go代碼片段所必須的資源(上下文環境)
- G(goroutine):一個G代表一個Go代碼片段。
? 一個M與一個P關聯之后,就形成一個G的運行環境(內核線程+上下文環境)。M與KSE一對一,M與P總是一對一(當一個M因系統調用阻塞(運行的G進入系統調用),此時P會與M分離開來,并與新建/空閑的M關聯),P與G一對多
| runtime/debug.SetMaxThreads | 設置M的最大數量 |
| runtime.GOMAXPORCS | 設置P的最大數量(可運行G隊列的數量) |
一、M、P、G
——M(Machine)
? 系統維護一個全局M列表(runtime.allm) 調度器維護一個空閑M列表(runtime.sched.midle)
——P(Processor)
? 系統維護一個全局P列表(runtime.allp) 調度器維護一個空閑P列表(runtime.sched.pidle)
? 每個P維護一個可運行G隊列(runtime.p.runq)(隊列滿則會分出一半給調度器可運行G隊列)與一個自由G列表(runtime.p.gfree)(已經運行完成的G,在欲啟用一個go語句時,會復用該列表中的G。當P中的自由G列表元素過多或過少時,調度器的自由G列表會與其進行轉移)
| Pidle | 當前P未與任何M存在關聯 |
| Prunning | 當前P正在與某個M關聯 |
| Psyscall | 當前P中運行的那個G正在進行系統調用 |
| Pgcstop | 運行時系統需要停止調度(系統開始垃圾回收的一些步驟,會將全部P設為此狀態) |
| Pdead | 當前P已經不會再被使用(調整最大P數量后,多余的P會被設為此狀態) |
? 非dead狀態的P在系統欲停止調度時都會被置于Pgcstop狀態。等到需要重啟調度時,會被統一置于Pidle狀態,即公平的接受再次調度。同時進入dead狀態的P,其可運行的G與自由的G都會被轉移到調度器的可運行G列表與自由G列表中。
——G(goroutine例程)
? 系統維護一個全局G列表(runtime.allgs)
? 調度器維護一個可運行G列表(runtime.sched.runqhead runtime.sched.runqtail) 一個自由G列表(runtime.sched.gfreeStack runtime.sched.gfreeNoStack)
| Gidle | 剛被新分配,還未初始化 |
| Grunnable | 正在可運行隊列中等待運行 |
| Grunning | 當前G正在運行 |
| Gsyscall | 當前G正在執行系統調用 |
| Gwaiting | 當前G正在阻塞 |
| Gdead | 當前G正在閑置 |
| Gcopystack | 當前G的棧正在被移動 |
? Gdead不同于Pdead,前者可以加入自由列表等待再次復用,后者只會被銷毀。
二、調度器
? Go調度器不是運行在某個專用內核線程中的程序,調度器會運行在幾乎所有已存在的M(內核線程)中。
? 調度器基本數據結構:空閑M列表,空閑P列表,可運行G隊列,自由G列表,此外還有幾個重要字段與需要停止調度的任務(Stop the world,STW)有關。
| gcwaiting | uint32 | 是否需要因一些任務而停止調度(比如垃圾回收) |
| stopwait | int32 | 需要停止但仍未停止的P數量 |
| stopnote | note | 實現與stopwait相關的事件通知機制 |
? 在停止調度前,gcwaiting被置為1,調度任務在發現這一狀態后,將當前P狀態置為Pgcstop并將stopwait字段減一。當stopwait減為0時,說明所有P已置為Pgcstop,此時利用stopnote喚醒待執行的任務(比如垃圾回收),之后恢復gcwaiting為0。
| sysmonwait | unit32 | 在停止調度期間系統監控任務是否在等待 |
| sysmonnote | note | 實現與sysmonwait相關的事件通知機制 |
? 這兩個字段針對系統監測任務,即在執行需要停止調度的任務之前,也需要停止系統監測任務。系統監測程序發現所有P都已經閑置或gcwaiting不為0,會將sysmonwait置為1,并使用sysmonnote暫停自身,結束后,再恢復這兩個狀態。
一輪調度
? 在啟動Go程序并完成初始化后,會啟動一輪調度使得封裝了main函數的G可以被調度運行,在G運行阻塞、結束、退出系統調用后都會引發一輪調度。
-
M與G的成對鎖定,是為了CGO準備。即C的函數庫會將數據存儲于內核線程,所以為了放止數據丟失,只能在一段時期內將G與特定M進行關聯。
-
當調度器為某個M1尋找到可執行G,但是檢查到某個M2已經與該G鎖定,則會立刻停止調度并停止M2,并將G交由M2運行(實際上是把M1的P交由M2),M1則尋找其他可執行G。——這意味著M2在運行鎖定的G前,不會做其他事,即資源被浪費。
-
全力查找可運行的G:若還未找到G,則調度器會停止該M,并在以后特定時刻喚醒重新查找。還未找到可運行G的M稱為自旋狀態。
-
啟用或停止M
(1)調度器調度一個M會預先檢查其是否與某個G鎖定;如果有,則會調用stoplockedm解除當前M與P的關聯并將P轉手,并暫停當前M的執行。
(2)調度器為M發現一個可運行的G,但是該G已經被其他M鎖定。會調用startlockedm將本地M的P轉手給鎖定的M,并暫停本地M的運行。放入空閑M列表。
(3)調度器發現有STW任務時,會調用gcstopm停止當前M。即釋放本地P(置為Pgcstop)并調用stopm。
(4)只有當有新工作,并且由空閑P時,調用startm才可以執行一個M。
操作作用 stopm() 停止當前M的執行 gcstopm() 為STW任務讓路,停止當前M,執行完畢后會被喚醒 stoplockedm() 停止已與某個G鎖定的M的執行,直到這個G可運行 startlockedm(gp *g) 喚醒與gp鎖定的那個M startm(p *p, spining bool) 喚醒或創建一個M去關聯P并開始執行
三、其他幾個要點
——g0、m0
? g0:系統中每個M擁有的特殊G。g0所擁有的內存稱為M的調度棧,對應于內核中線程的棧,即OS線程棧。用于執行調度、垃圾回收、棧管理等。
? gsignal:系統中每個M擁有的特殊G,用來處理信號。即信號棧。
? m0:Go程序的第一個內核線程runtime.g0,用于執行引導程序。
——調度器鎖和原子操作
? 每個M都有可能執行調度任務,而這些任務可能會并發進行,所以需要在讀寫一些全局變量時進行調度器鎖保護。比如sched.stopwait(STW任務時記錄需要停止的M)、sched.nmidle(對空閑M計數)、對核心元素容器進行存取(runtime.allp, runtime.sched.runqhead)。
? 同時也采用原子操作保護一些變量。比如sched.spining(對自旋M計數)、sched.ngsys(對系統G計數)、切換G狀態。
——調整GC
? Go的GC基于CMS(Concurrent Mark-Sweep)算法。調度器會適時調度GC相關任務執行,系統監測任務也會在必要時強制執行。有三種執行模式:
- gcBackgroundMode:并發執行垃圾收集和清掃。
- gcForceMode:串行的執行垃圾收集(即執行時停止調度),并發的執行垃圾清掃。
- gcForceBlockMode:串行的執行垃圾收集和清掃。
總結
以上是生活随笔為你收集整理的Go学习笔记—并发高级的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: arcgis js 4.x 地图中加入图
- 下一篇: 断点续传和下载原理分析