<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 6.5 線程管理 Go 語言既然專門將線程進一步抽象為 Goroutine,自然也就不希望我們對線程做過多的操作,事實也是如此, 大部分的用戶代碼并不需要線程級的操作。但某些情況下,當需要 使用 cgo 調用 C 端圖形庫(如 GLib)時,甚至需要將某個 Goroutine 用戶態代碼一直在主線程上執行。 我們已經知道了`runtime.LockOSThread`會將當前 Goroutine 鎖在一個固定的 OS 線程上執行, 但是一旦開放了鎖住某個 OS 線程后,會連帶產生一些副作用。比如當系統級的編程實踐總是需要對線程進行操作, 尤其是當用戶態代碼通過系統調用將 OS 線程所在的 Linux namespace 進行修改、把線程私有化時(系統調用`unshare`和標志位`CLONE_NEWNS`), 其他 Goroutine 已經不再適合在此 OS 線程上執行。這時候不得不將 M 永久的從運行時中移出, 通過[6.3 調度循環](https://golang.design/under-the-hood/zh-cn/part2runtime/ch06sched/exec)一節的介紹,我們知道`LockOSThread/UnlockOSThread`也是目前唯一一個能夠讓 M 退出的做法(將 Goroutine 鎖在 OS 線程上,且在 Goroutine 死亡退出時不調用 Unlock 方法)。 本節便進一步研究 Go 語言對用戶態線程操作的支持和與之相關的運行時線程的管理。 ## 6.5.1 LockOSThread LockOSThread 和 UnlockOSThread 在運行時包中分別提供了私有和公開的方法。 運行時私有的 lockOSThread 非常簡單: ``` //go:nosplit func lockOSThread() { getg().m.lockedInt++ dolockOSThread() } ``` 因為整個運行時只有在`runtime.main`調用`main.init`、和 cgo 的 C 調用 Go 時候才會使用, 其中`main.init`其實也是為了 cgo 里 Go 調用某些 C 圖形庫時需要主線程支持才使用的。 因此不需要做過多復雜的處理,直接在 m 上進行計數 (計數的原因在于安全性和時鐘上的一些處理,防止用戶態代碼誤用, 例如只調用了 Unlock 而沒有先調用 Lock \[Mills, 2017\]), 而后調用`dolockOSThread`將 g 與 m 互相鎖定: ``` // dolockOSThread 在修改 m.locked 后由 LockOSThread 和 lockOSThread 調用。 // 在此調用期間不允許搶占,否則此函數中的 m 可能與調用者中的 m 不同。 //go:nosplit func dolockOSThread() { if GOARCH == "wasm" { return // no threads on wasm yet } _g_ := getg() _g_.m.lockedg.set(_g_) _g_.lockedm.set(_g_.m) } ``` 而用戶態的公開方法則不同,還額外增加了一個模板線程的處理(隨后解釋),這也解釋了運行時其實并不希望 模板線程的存在,只有當需要時才會懶加載: ``` func LockOSThread() { if atomic.Load(&amp;newmHandoff.haveTemplateThread) == 0 &amp;&amp; GOOS != "plan9" { // 如果我們需要從鎖定的線程啟動一個新線程,我們需要模板線程。 // 當我們處于一個已知良好的狀態時,立即啟動它。 startTemplateThread() } _g_ := getg() _g_.m.lockedExt++ if _g_.m.lockedExt == 0 { _g_.m.lockedExt-- panic("LockOSThread nesting overflow") } dolockOSThread() } ``` ## 6.5.2 UnlockOSThread Unlock 的部分非常簡單,減少計數,再實際 dounlock: ``` func UnlockOSThread() { _g_ := getg() if _g_.m.lockedExt == 0 { return } _g_.m.lockedExt-- dounlockOSThread() } //go:nosplit func unlockOSThread() { _g_ := getg() if _g_.m.lockedInt == 0 { systemstack(badunlockosthread) } _g_.m.lockedInt-- dounlockOSThread() } ``` 而且并無特殊處理,只是簡單的將`lockedg`和`lockedm`兩個字段清零: ``` // dounlockOSThread 在更新 m-&gt;locked 后由 UnlockOSThread 和 unlockOSThread 調用。 // 在此調用期間不允許搶占,否則此函數中的 m 可能與調用者中的 m 不同。 //go:nosplit func dounlockOSThread() { if GOARCH == "wasm" { return // no threads on wasm yet } _g_ := getg() if _g_.m.lockedInt != 0 || _g_.m.lockedExt != 0 { return } _g_.m.lockedg = 0 _g_.lockedm = 0 } ``` ## 6.5.3 lockedg/lockedm 與調度循環 一個很自然的問題,為什么簡單的設置 lockedg 和 lockedm 之后就能保證 g 只在一個 m 上執行了? 其實我們已經在調度循環中見過與之相關的代碼了: ``` // 調度器的一輪:找到 runnable Goroutine 并進行執行且永不返回 func schedule() { _g_ := getg() if _g_.m.locks != 0 { throw("schedule: holding locks") } // m.lockedg 會在 lockosthread 下變為非零 if _g_.m.lockedg != 0 { stoplockedm() execute(_g_.m.lockedg.ptr(), false) // 永不返回 } ... } ``` 調度循環在發現當前的 m 存在請求鎖住執行的 g 時,不會進入后續 g 的偷取過程, 相反會直接調用`stoplockedm`,將當前的 m 和 p 解綁,并 park 當前的 m, 直到可以再次調度 lockedg 為止,獲取 p 并通過`execute`直接調度 lockedg , 從而再次進入調度循環: ``` // 停止當前正在執行鎖住的 g 的 m 的執行,直到 g 重新變為 runnable。 // 返回獲得的 P func stoplockedm() { _g_ := getg() if _g_.m.lockedg == 0 || _g_.m.lockedg.ptr().lockedm.ptr() != _g_.m { throw("stoplockedm: inconsistent locking") } if _g_.m.p != 0 { // 調度其他 M 來運行此 P _p_ := releasep() handoffp(_p_) } incidlelocked(1) // 等待直到其他線程可以再次調度 lockedg notesleep(&amp;_g_.m.park) noteclear(&amp;_g_.m.park) status := readgstatus(_g_.m.lockedg.ptr()) if status&amp;^_Gscan != _Grunnable { print("runtime:stoplockedm: g is not Grunnable or Gscanrunnable\n") dumpgstatus(_g_) throw("stoplockedm: not runnable") } acquirep(_g_.m.nextp.ptr()) _g_.m.nextp = 0 } ``` ## 6.5.4 模板線程 前面已經提到過,鎖住系統線程帶來的隱患就是某個線程的狀態可能被用戶態代碼過分的修改, 從而不再具有產出新線程的能力,模板線程就提供了一個備用線程,不會執行 g,只用于創建安全的 m。 模板線程會在第一次調用`LockOSThread`的時候被創建,并將`haveTemplateThread`標記為已經存在模板線程: ``` // 如果模板線程尚未運行,則startTemplateThread將啟動它。 // // 調用線程本身必須處于已知良好狀態。 func startTemplateThread() { if GOARCH == "wasm" { // no threads on wasm yet return } if !atomic.Cas(&amp;newmHandoff.haveTemplateThread, 0, 1) { return } newm(templateThread, nil) } ``` `tempalteThread`這個函數會在 m 正式啟動時被調用: ``` // 創建一個新的 m. 它會啟動并調用 fn 或調度器 // fn 必須是靜態、非堆上分配的閉包 // 它可能在 m.p==nil 時運行,因此不允許 write barrier //go:nowritebarrierrec func newm(fn func(), _p_ *p) { // 分配一個 m mp := allocm(_p_, fn) ... } //go:yeswritebarrierrec func allocm(_p_ *p, fn func()) *m { ... mp := new(m) mp.mstartfn = fn ... } func mstart1() { ... // 執行啟動函數 if fn := _g_.m.mstartfn; fn != nil { fn() } ... } ``` 這個`newmHandoff`負責并串聯了所有新創建的 m: ``` // newmHandoff 包含需要新 OS 線程的 m 的列表。 // 在 newm 本身無法安全啟動 OS 線程的情況下,newm 會使用它。 var newmHandoff struct { lock mutex // newm 指向需要新 OS 線程的M結構列表。 該列表通過 m.schedlink 鏈接。 newm muintptr // waiting 表示當 m 列入列表時需要通知喚醒。 waiting bool wake note // haveTemplateThread 表示 templateThread 已經啟動。沒有鎖保護,使用 cas 設置為 1。 haveTemplateThread uint32 } ``` 而模板線程本身不會退出,只會在需要的時,創建 m: ``` // templateThread是處于已知良好狀態的線程,僅當調用線程可能不是良好狀態時, // 該線程僅用于在已知良好狀態下啟動新線程。 // // 許多程序不需要這個,所以當我們第一次進入可能導致在未知狀態的線程上運行的狀態時, // templateThread會懶啟動。 // // templateThread 在沒有 P 的 M 上運行,因此它必須沒有寫障礙。 // //go:nowritebarrierrec func templateThread() { lock(&amp;sched.lock) sched.nmsys++ checkdead() unlock(&amp;sched.lock) for { lock(&amp;newmHandoff.lock) for newmHandoff.newm != 0 { newm := newmHandoff.newm.ptr() newmHandoff.newm = 0 unlock(&amp;newmHandoff.lock) for newm != nil { next := newm.schedlink.ptr() newm.schedlink = 0 newm1(newm) newm = next } lock(&amp;newmHandoff.lock) } // 等待新的創建請求 newmHandoff.waiting = true noteclear(&amp;newmHandoff.wake) unlock(&amp;newmHandoff.lock) notesleep(&amp;newmHandoff.wake) } } ``` 當創建好 m 后,模板線程會休眠,直到創建新的 m 時候會被喚醒,這個我們在分析調度循環的時候已經看到過了: ``` // 創建一個新的 m. 它會啟動并調用 fn 或調度器 // fn 必須是靜態、非堆上分配的閉包 // 它可能在 m.p==nil 時運行,因此不允許 write barrier //go:nowritebarrierrec func newm(fn func(), _p_ *p) { ... if gp := getg(); gp != nil &amp;&amp; gp.m != nil &amp;&amp; (gp.m.lockedExt != 0 || gp.m.incgo) &amp;&amp; GOOS != "plan9" { // 我們處于一個鎖定的 M 或可能由 C 啟動的線程。這個線程的內核狀態可能 // 很奇怪(用戶可能已將其鎖定)。我們不想將其克隆到另一個線程。 // 相反,請求一個已知狀態良好的線程來創建給我們的線程。 // // 在 plan9 上禁用,見 golang.org/issue/22227 // // TODO: This may be unnecessary on Windows, which // doesn't model thread creation off fork. lock(&amp;newmHandoff.lock) if newmHandoff.haveTemplateThread == 0 { throw("on a locked thread with no template thread") } mp.schedlink = newmHandoff.newm newmHandoff.newm.set(mp) if newmHandoff.waiting { newmHandoff.waiting = false // 喚醒 m, spinning -&gt; non-spinning notewakeup(&amp;newmHandoff.wake) } unlock(&amp;newmHandoff.lock) return } newm1(mp) } ``` ## 小結 LockOSThread 并不是什么優秀的特性,相反它卻給 Go 運行時調度器帶來了諸多管理上的難題。 它的存在僅僅只是需要提供對上個世紀 C 編寫的諸多遺產提供必要支持,倘若 Go 的基礎庫能夠更加豐富, 這項特性可能不復存在。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看