1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        『每周譯Go』Go 的搶占式調(diào)度(文末有彩蛋)

        共 4396字,需瀏覽 9分鐘

         ·

        2021-05-15 12:59


        • 原文地址:https://dtyler.io/articles/2021/03/29/goroutine_preemption_en/

        • 原文作者:Hidetatsu

        • 本文永久鏈接:https://github.com/gocn/translator/blob/master/2021/w19_Preemption_in_Go.md

        • 譯者:lsj1342

        • 校對:guzzsek、fivezh



        我正在研究 Go 中 goroutine 的搶占。如果您能指出文中任何錯誤并告知我,將感激不盡。

        Go1.14 版本中的搶占行為已經(jīng)發(fā)生了變化。在 Go1.14 中,goroutine 是“異步搶占”的,如發(fā)行版本所述。這意味著什么呢?

        首先,讓我們看一個簡單的例子。思考下面的 Go 程序。

        package main

        import (
            "fmt"
        )

        func main() {
            go fmt.Println("hi")
            for {
            }
        }

        在主函數(shù)中,啟動了一個只輸出 “hi” 的 goroutine。此外,存在一個無限循環(huán) for {}

        如果我們攜帶參數(shù) GOMAXPROCS=1 運行程序時,將發(fā)生什么呢?程序似乎在輸出 “hi” 后,由于無限循環(huán)而沒有任何反應。實際上,我使用 Go1.14 或更高版本運行該程序時(我使用 Go1.16 上運行了該程序(在 WSL2 上的 Ubuntu )),它能按照預期工作。

        有兩種方法可以阻止此程序運行。一種是使用 1.14 之前的 Go 版本運行它。另一種是運行它時攜帶參數(shù) GODEBUG=asyncpreemptoff=1。

        當我在本地計算機上嘗試時,它的工作方式如下。

        $ GOMAXPROCS=1 GODEBUG=asyncpreemptoff=1 go run main.go
        # it blocks here

        程序沒有輸出 “hi” 。在描述為什么會發(fā)生這種情況之前,讓我先說明幾種使該程序按預期方式運行的方法。

        一種方法是在循環(huán)中添加以下代碼。



        *************** package main
        *** 2,11 ****
        --- 2,13 ----
          
          import (
              "fmt"
        +     "runtime"
          )
          
          func main() {
              go fmt.Println("hi")
              for {
        +         runtime.Gosched()
              }
          }

        runtime.Gosched() 類似于 POSIX 的 sched_yield。sched_yield 強制當前線程放棄 CPU,以便其他線程可以運行。之所以命名為 Gosched,因為 Go 中是 goroutine,而不是線程(這是一個猜測)。換句話說,顯式調(diào)用 runtime.Gosched() 將強制對 goroutines 進行重新安排,并且我們期望將當前運行的 goroutine 切換到另一個。

        另一種方法是使用 GOEXPERIMENT=preemptibleloops。它強制 Go 運行時在“循環(huán)”上進行搶占。這種方式不需要更改代碼。

        協(xié)作式調(diào)度 vs 搶占式調(diào)度

        首先,有兩種主要的多任務調(diào)度方法:“協(xié)作”和“搶占”。協(xié)作式多任務處理也稱為“非搶占”。在協(xié)作式多任務處理中,程序的切換方式取決于程序本身?!皡f(xié)作”一詞是指這樣一個事實:程序應設計為可互操作的,并且它們必須彼此“協(xié)作”。在搶占式多任務處理中,程序的切換交給操作系統(tǒng)。調(diào)度是基于某種算法的,例如基于優(yōu)先級,F(xiàn)CSV,輪詢等。

        那么現(xiàn)在,goroutine 的調(diào)度是協(xié)作式還是搶占式的?至少在 Go1.13 之前,它是協(xié)作式的。

        我沒有找到任何官方文檔,但是我發(fā)現(xiàn)在以下情況會進行 goroutine 切換(并不詳盡)。

        等待讀取或?qū)懭胛淳彌_的通道 由于系統(tǒng)調(diào)用而等待 由于 time.Sleep() 而等待 等待互斥量釋放 此外,Go 會啟動一個線程,一直運行著“sysmon”函數(shù),該函數(shù)實現(xiàn)了搶占式調(diào)度(以及其他諸如使網(wǎng)絡處理的等待狀態(tài)變?yōu)榉亲枞麪顟B(tài))的功能。sysmon 運行在 M(Machine,實際上是一個系統(tǒng)線程),且不需要 P(Processor)。術語 M,P 和 G 在類似這樣的各種文章中都有解釋。我建議您在需要時參考此類文章。

        當 sysmon 發(fā)現(xiàn) M 已運行同一個 G(Goroutine)10ms 以上時,它會將該 G 的內(nèi)部參數(shù) preempt 設置為 true。然后,在函數(shù)序言中,當 G 進行函數(shù)調(diào)用時,G 會檢查自己的 preempt 標志,如果它為 true,則它將自己與 M 分離并推入“全局隊列”?,F(xiàn)在,搶占就成功完成。順便說一下,全局隊列是與“本地隊列”不同的隊列,本地隊列是存儲 P 具有的 G。全局隊列有以下幾個作用。

        存儲那些超過本地隊列容量(256)的 G 存儲由于各種原因而等待的 G 存儲由搶占標志分離的 G 這是 Go1.13 及其之前版本的實現(xiàn)?,F(xiàn)在,您將了解為什么上面的無限循環(huán)代碼無法按預期工作。for{} 僅僅是一個死循環(huán),所以如前所述它不會觸發(fā) goroutine 切換。您可能會想,“sysmon 是否設置了搶占標志,因為它已經(jīng)運行了 10ms 以上?” 然而,如果沒有函數(shù)調(diào)用,即使設置了搶占標志,也不會進行該標志的檢查。如前所述,搶占標志的檢查發(fā)生在函數(shù)序言中,因此不執(zhí)行任何操作的死循環(huán)不會發(fā)生搶占。

        是的,隨著 Go1.14 中引入“非協(xié)作式搶占”(異步搶占),這種行為已經(jīng)改變。

        “異步搶占”是什么意思?

        讓我們總結到目前為止的要點;Go 具有一種稱為“sysmon”的機制,可以監(jiān)視運行 10ms 以上的 goroutine 并在必要時強制搶占。但是,由于它的工作方式,在 for{} 的情況下并不會發(fā)生搶占。

        Go1.14 引入非協(xié)作式搶占,即搶占式調(diào)度,是一種使用信號的簡單有效的算法。

        首先,sysmon 仍然會檢測到運行了 10ms 以上的 G(goroutine)。然后,sysmon 向運行 G 的 P 發(fā)送信號(SIGURG)。Go 的信號處理程序會調(diào)用P上的一個叫作 gsignal 的 goroutine 來處理該信號,將其映射到 M 而不是 G,并使其檢查該信號。gsignal 看到搶占信號,停止正在運行的 G。

        由于此機制會顯式發(fā)出信號,因此無需調(diào)用函數(shù),就能將正在運行死循環(huán)的 goroutine 切換到另一個 goroutine。

        通過使用信號的異步搶占機制,上面的代碼現(xiàn)在就可以按預期工作。GODEBUG=asyncpreemptoff=1可用于禁用異步搶占。

        順便說一句,他們選擇使用 SIGURG,是因為 SIGURG 不會干擾現(xiàn)有調(diào)試器和其他信號的使用,并且因為它不在 libc 中使用。(參考)

         總結

        不執(zhí)行任何操作的無限循環(huán)不會將 CPU 傳遞給其他 goroutine,并不意味著 Go1.13 之前的機制是不好的。正如 @davecheney 所說,通常不認為這是一個特殊問題。起初,異步搶占不是為了解決無限循環(huán)問題引出的。

        盡管異步搶占的引入使調(diào)度更具搶占性,但也有必要在 GC 期間更加謹慎地處理“不安全點”。在這方面對實現(xiàn)上的考慮也非常有趣。有興趣的讀者可以自己閱讀議題:非協(xié)作式 goroutine 搶占。

        參考

        • Proposal: Non-cooperative goroutine preemption 
        • runtime: non-cooperative goroutine preemption
        • runtime: tight loops should be preemptible
        • runtime: golang scheduler is not preemptive - it’s cooperative?
        • Source file src/runtime/preempt.go
        • Goroutine preemptive scheduling with new features of go 1.14
        • Go: Goroutine and Preemption
        • At which point a goroutine can yield?
        • Go: Asynchronous Preemption
        • go routine blocking the others one [duplicate]
        • (Ja) Golangのスケジューラあたりの話
        • (Ja) goroutineがスイッチされるタイミング

        NEWS

        在這次 GopherChina 2021大會上,曹春暉老師將與 Gopher 們分享 “Go 的搶占式調(diào)度”相關的精彩內(nèi)容。


        01

        主講老師:

        曹春暉(Xargin)前螞蟻金服技術專家,Go 語言 contributor 對Go語言工程化落地有多年實踐經(jīng)驗。


        Go 語言 contributor,貢獻過性能優(yōu)化的 PR且被官方采用


        出版有暢銷書 《Go 語言高級編程》


        主導過巨頭公司數(shù)據(jù)中臺建設,開發(fā)的平臺 qps 超過 30w


        優(yōu)化過部署在幾十萬實例上的基礎設施軟件



        想要加入組織的 Gopher 們,請掃碼入群,即可獲得GopherChina大會的實時動向~


        點擊閱讀原文,即刻獲得早鳥票~

        瀏覽 29
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产乱子伦对白视频在线观看 | 天天拍天天干天天射 | 一级特黄录像免费观看 | 日韩三级电影在线观看 | 国产激情直播 | 欧美日日日日bbbbb视频 涩爱av色老久久精品偷偷鲁 | 国产69成人精品视频免费APP | 他添的我好湿好爽h视频软件 | 大尺度床戏揉捏胸 | 阿娇张开腿实干12次视频 |