Goroutine設計原理

爲什麼Golang需要單獨開發一個Goroutine?

  • 開銷問題:

    POSIX的thread API雖然能夠提供豐富的API,例如配置自己的CPU親和性,申請資源等等,線程在得到了很多與進程相同的控制權的同時,開銷也非常的大,在Goroutine中則不需這些額外的開銷,所以一個Golang的程序中可以支持10w級別的Goroutine。

  • 調度性能:

    在Golang的程序中,操作系統級別的線程調度,通常不會做出合適的調度決策。例如在GC時,內存必須要達到一個一致的狀態。在Goroutine機制裏,Golang可以控制Goroutine的調度,從而在一個合適的時間進行GC。

Goroutine的實現原理

  • 兩種備選方案
    • (M:1)多個用戶態的線程對應一個系統線程,它可以做快速的上下文切換。缺點是不能有效利用多核CPU
    • (1:1)一個用戶態的線程對應一個系統線程,它可以利用多核機制,但上下文切換需要消耗額外的資源
  • Golang的做法

    • (M:N)Golang採取了一種多對多的方案。M個用戶線程對應N個系統線程,缺點增加了調度器的實現難度
  • 角色:

    • M: 代表了系統線程,由操作系統管理
    • G:Goroutine的實體,包括了調用棧,重要的調度信息,例如channel等。
    • P:銜接M和G的調度上下文,它負責將等待執行的G與M對接。

      P的數量由環境變量中的GOMAXPROCS決定,通常來說它是和核心數對應,例如在4Core的服務器上回啓動4個線程。G會有很多個,每個P會將Goroutine從一個就緒的隊列中做Pop操作,爲了減小鎖的競爭,通常情況下每個P會負責一個隊列。

  • 掛起

    在Goroutine需要執行一個系統調用時,由於M是一個線程,所以必須等待它執行完才能執行其他的Goroutine。當一個新的Goroutine產生,M需要保證會有另外的一個M能夠執行這個G,簡單來說,當一個M進行系統調用,需要保證有另外的一個M能夠繼續執行Go代碼。

    • 何時恢復?

      當系統調用返回時,M需要找到一個對應的P,以便能夠運行Goroutine,它首先會嘗試從其他線程中竊取一個P,如果不成功,它會將Goroutine放在一個全局的隊列中,並將自己放在thread cache中。

    • 如何竊取

      這裏有篇paper來描述這個設計:work-steal.

      簡單來說,當隊列不平衡時,會從其他隊列中截取一部分Goroutine到P上進行調度。

參考鏈接

https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit#
https://morsmachine.dk/go-scheduler
(翻譯)[https://www.zhihu.com/question/20862617]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章