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

部落格猿馬甲哥發表於2022-03-15

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

相關文章