位元組一面:go的協程相比執行緒,輕量在哪?

1. 用戶態和內核態

Linux整個體系分為用戶態和內核態(或者叫用戶空間和內核空間), 那內核態究竟是什麼呢?

本質上我們所說的內核態, 它是一種特殊的軟體程式,特殊在哪?
統籌電腦的硬體資源,例如協調CPU資源、分配記憶體資源、並且提供穩定的環境供應用程式運行。

2. 為什麼執行緒切換會導致用戶態和內核態的切換?

  • 執行緒是cpu調度的基本單位,進程是資源佔有的基本單位。
  • 因為執行緒中的程式碼是在用戶態運行,而執行緒的調度是在內核態,所以執行緒切換會觸發用戶態和內核態的切換。
  • 執行緒上下文切換的代價是高昂的:上下文切換的延遲取決於不同的因素,大概是50到100 ns左右,考慮到硬體平均在每個核心上每ns執行12條指令,那麼一次上下文切換可能會花費600到1200條指令的延遲時間。

3. 導致執行緒上下文切換的時機

<1>. 自發性上下文切換 自發性上下文切換指執行緒由於自身因素導致的切出
Thread.sleep() 執行緒主動休眠
object.wait() 執行緒等待鎖
Thread.yield() 當前執行緒主動讓出CPU,如果有其他就緒執行緒就執行其他執行緒,如果沒有則繼續當前執行緒
oThread.join() 阻塞發起調用的執行緒,直到oThread執行完畢

<2>. 非自發性上下文切換:執行緒由於執行緒調度器的原因被迫切出
執行緒的時間片用完
高優先順序執行緒搶佔
虛擬機的垃圾回收動作

4. 執行緒切換的開銷

<1>. 直接開銷
保存/恢復上下文所需的開銷
執行緒調度器調度執行緒的開銷
<2>. 間接開銷
重新載入高速快取
上下文切換可能導致 一級快取被沖刷,寫入下一級快取或記憶體

5. go的協程輕量級體現在哪?

如上面所述,執行緒切換會導致 用戶態和內核態的切換,其中內核態耗時較長,且不受用戶程式碼控制。

go將goroutine的調度維持在用戶態, 這是由GPM中的P Process來完成的,做用戶態任務的調度器,功能類比於常規的作業系統執行緒調度器,所以又被稱為邏輯處理器。

P 是G、M之間的橋樑,調度器對於goroutine的調度,很明顯也會有切換,這個切換是很輕量的: 只涉及PC SP DX三個暫存器的值的修改;而對比執行緒的上下文切換則需要陷入內核模式、以及16個暫存器的刷新。

6. GO GMP 調度方式

  • 由邏輯處理器P調度協程G進系統執行緒M (若本地隊列沒有G,從其他隊列/全局隊列偷取G),
  • 執行緒M執行G, 遇到[系統調用], G和M分離,拿新的M去接管原邏輯處理器P

請仔細閱讀上圖,出處不可考證,感謝原圖作者。

這裡特意指出網路IO操作不會走上圖的模型,否則要分配的系統執行緒M依然很多,程式很快就爆滿了。這時G會和邏輯處理器P分離,並移動到netpoller,一旦網路輪詢器通知網路讀/寫就緒,對應G就會重新分配到邏輯器處理器上來完成操作, 在此期間原系統執行緒M可以去做別的G。

ref