一、線程調度
1、早期單線程操作系統
- 一切的軟件都是跑在操作系統上,真正用來干活(計算)的是 CPU
- 早期的操作系統每個程序就是一個進程,直到一個程序運行完,才能進行下一個進程,就是“單進程時代”
- 一切的程序只能串行發生
2、多進程/線程時代?
- 在多進程/多線程的操作系統中,就能解決了阻塞的問題,因為一個進程阻塞 cpu 可以立刻切換到其他進程中去執行
- 而且調度 cpu 的算法可以保證在運行的進程都可以被分配到 cpu 的運行時間片
- 這樣從宏觀來看,似乎多個進程是在同時被運行
- 但新的問題就又出現了,進行擁有太多的資源,進程的創建、切換、銷毀、都會占用很長的時間
- CPU 雖然利用起來了,但如果進程過多,CPU 有很大的一部分都被用來進行進程調度了
- 大量的進程/線程都出現了新的問題
- 高內存占用
- 調度的高消耗 CPU
- 進程虛擬內存會占用 4GB[32位操作系統],而線程也要大約 4MB
3、Go 協程 goroutine?
- Go 中,協程被稱為 goroutine,它非常輕量,一個 goroutine 只占幾 KB,并且這幾 KB 就足夠 goroutine 運行完
- 這就能在有限的內存空間內支持大量 goroutine;支持了更多的并發
- 雖然一個 goroutine 的棧只占幾 KB,但實際是可伸縮的,如果需要更多內容,runtime 會自動為 goroutine 分配
- Goroutine 特點:
- 占用內存更小(幾 KB)
- 調度更靈活(runtime 調度)
4、協程域線程區別
- 協程跟線程是有區別的,線程由 CPU 調度是搶占式的
- 協程由用戶調度是協作式的,一個協程讓出 cpu 后,才執行下一個協程
二、調度器 GMP 模型
- G:goroutine(協程)
- M:thread(內核線程,不是用戶態線程)
- P:processer(調度器)?
1、GM 模型
- G(協程)通常在代碼里用 go 關鍵字執行一個方法,那么就等于起了一個 G
- M(內核線程)操作系統內核其實看不見 G 和 P,只知道自己在執行一個線程
- G 和 P 都是在用戶層上的實現
- 并發量小的時候還好,當并發量大了,這把大鎖,就成了性能瓶頸
- GMP 由來
- 基于沒有什么是加一個中間層不能解決的思路,golang在原有的GM的基礎上加入了一個調度器P
- 可以簡單理解為是在G和M中間加了個中間層
- 于是就有了現在的GMP模型里的P
2、GMP模型
三、GMP流程分析?
- 我們通過go func()來創建一個goroutine
1、P本地隊列獲取G
- M想要運行G,就要先獲取P,然后從P的本地隊列獲取G
2、本地隊列中G移動到全局隊列
- ?新建G時,新G會優先加入到P的本地隊列
- 如果本地隊列滿了,則會把本地隊列中一半的G移動到全局隊列
3、從其他P本地隊列的G放到自己P隊列
- ?如果全局隊列為空時,M會從其他P的本地隊列偷(stealing)一半G放到自己P的本地隊列
4、M從P獲取下一個G,不斷重復
- M運行G,G執行之后,M會從P獲取下一個G,不斷重復下去