<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 6.7 執行棧管理 ## 6.7.1 Goroutine 棧結構 Goroutine 是一個 g 對象,g 對象的前三個字段描述了它的執行棧: ``` // stack 描述了 Goroutine 的執行棧,棧的區間為 [lo, hi),在棧兩邊沒有任何隱式數據結構 // 因此 Go 的執行棧由運行時管理,本質上分配在堆中,比 ulimit -s 大 type stack struct { lo uintptr hi uintptr } // gobuf 描述了 Goroutine 的執行現場 type gobuf struct { sp uintptr pc uintptr g guintptr ctxt unsafe.Pointer ret sys.Uintreg lr uintptr bp uintptr } type g struct { // stack 描述了實際的棧內存:[stack.lo, stack.hi) stack stack // stackguard0 是對比 Go 棧增長的 prologue 的棧指針 // 如果 sp 寄存器比 stackguard0 小(由于棧往低地址方向增長),會觸發棧拷貝和調度 // 通常情況下:stackguard0 = stack.lo + StackGuard,但被搶占時會變為 StackPreempt stackguard0 uintptr // stackguard1 是對比 C 棧增長的 prologue 的棧指針 // 當位于 g0 和 gsignal 棧上時,值為 stack.lo + StackGuard // 在其他棧上值為 ~0 用于觸發 morestackc (并 crash) 調用 stackguard1 uintptr ... // sched 描述了執行現場 sched gobuf ... } ``` ~~~ <-- _StackPreempt 高地址 Goroutine stack +-------------------+ <-- _g_.stack.hi | | +-------------------+ | | +-------------------+ | | +-------------------+ <-- _g_.sched.sp | | +-------------------+ | | +-------------------+ | | +-------------------+ | | +-------------------+ .... | | +-------------------+ <-- _g_.stackguard0 | | | | +-------------------+ | | _StackSmall | | | | +-------------------+ | --- | | | +-------------------+ | _StackGuard | | | +-------------------+ <-- _g_.stack.lo 低地址 ~~~ ## 6.7.2 執行棧初始化 執行棧可以在函數執行完畢后,專門被垃圾回收整個回收掉,從而將它們單獨管理起來能夠利于垃圾回收器的統一回收: ``` // 具有可用棧的 span 的全局池 // 每個棧均根據其大小會被分配一個 order = log_2(size/FixedStack) // 每個 order 都包含一個可用 mspan 鏈表 var stackpool [_NumStackOrders]struct { item stackpoolItem _ [cpu.CacheLinePadSize - unsafe.Sizeof(stackpoolItem{})%cpu.CacheLinePadSize]byte } //go:notinheap type stackpoolItem struct { mu mutex span mSpanList } var stackLarge struct { lock mutex free [heapAddrBits - pageShift]mSpanList // 按 log_2(s.npages) 階組成的多個鏈表 } ``` `stackpool/stackLarge`均為全局變量,他們均為`mspan`的雙向鏈表,他們的初始化邏輯非常簡單, 既將整個鏈表初始化為空鏈,不分配節點: ``` //go:notinheap type mSpanList struct { // 不帶頭結點的 mspan 雙向鏈表 first *mspan last *mspan } func (list *mSpanList) init() { list.first = nil list.last = nil } ``` `stackpool`和`stackLarge`的初始化僅僅就是將這兩個鏈表中不同階的 mspan 鏈表進行初始化: ``` func stackinit() { ... for i := range stackpool { stackpool[i].item.span.init() } for i := range stackLarge.free { stackLarge.free[i].init() } } ``` ## 6.7.3 G 的創生 一個 Goroutine 的創建通過`newproc`來完成,在調用這個函數之前,Goroutine 還尚未存在, 只有一個入口地址及參數的大小,我們通過下面的例子來理解: ``` package main func hello(msg string) { println(msg) } func main() { go hello("hello world") // 7-8 行 } ``` 其編譯后的形式為: ``` TEXT main.main(SB) main.go main.go:7 0x104df70 65488b0c2530000000 MOVQ GS:0x30, CX ... main.go:8 0x104df8d 488d055ed10100 LEAQ go.string.*+1874(SB), AX main.go:8 0x104df94 4889442410 MOVQ AX, 0x10(SP) main.go:8 0x104df99 48c74424180b000000 MOVQ $0xb, 0x18(SP) main.go:8 0x104dfa2 c7042410000000 MOVL $0x10, 0(SP) main.go:8 0x104dfa9 488d05b80c0200 LEAQ go.func.*+67(SB), AX main.go:8 0x104dfb0 4889442408 MOVQ AX, 0x8(SP) main.go:8 0x104dfb5 e876cefdff CALL runtime.newproc(SB) ... ``` 具體的傳參過程: ``` LEAQ go.string.*+1874(SB), AX // 將 "hello world" 的地址給 AX MOVQ AX, 0x10(SP) // 將 AX 的值放到 0x10 MOVL $0x10, 0(SP) // 將最后一個參數的位置存到棧頂 0x00 LEAQ go.func.*+67(SB), AX // 將 go 語句調用的函數入口地址給 AX MOVQ AX, 0x8(SP) // 將 AX 存入 0x08 CALL runtime.newproc(SB) // 調用 newproc ``` 這個過程里我們基本上可以看到棧是這樣排布的: ~~~ 棧布局 | | 高地址 | | +-----------------+ | &"hello world" | 0x10 +-----------------+ <-- fn + sys.PtrSize | hello | 0x08 +-----------------+ <-- fn | siz | 0x00 +-----------------+ <-- SP | newproc PC | +-----------------+ callerpc: 要運行的 Goroutine 的 PC | | | | 低地址 ~~~ ``` func newproc(siz int32, fn *funcval) { // 從 fn 的地址增加一個指針的長度,從而獲取第一參數地址 argp := add(unsafe.Pointer(&amp;fn), sys.PtrSize) gp := getg() // 獲取調用方 PC/IP 寄存器值 pc := getcallerpc() // 用 g0 系統棧創建 Goroutine 對象 // 傳遞的參數包括 fn 函數入口地址, argp 參數起始地址, siz 參數長度, gp(g0),調用方 pc(Goroutine) systemstack(func() { newproc1(fn, (*uint8)(argp), siz, gp, pc) }) } ``` 當調用`newproc1`,會嘗試獲取一個已經分配好的 g,否則會直接進入創建: ``` func newproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) { ... newg := gfget(_p_) // 根據 p 獲得一個新的 g // 初始化階段,gfget 是不可能找到 g 的 // 也可能運行中本來就已經耗盡了 if newg == nil { newg = malg(_StackMin) // 創建一個擁有 _StackMin 大小的棧的 g casgstatus(newg, _Gidle, _Gdead) // 將新創建的 g 從 _Gidle 更新為 _Gdead 狀態 allgadd(newg) // 將 Gdead 狀態的 g 添加到 allg,這樣 GC 不會掃描未初始化的棧 } ... } ``` 從而通過`malg`分配一個具有最小棧的 Goroutine: ``` // 分配一個新的 g 結構, 包含一個 stacksize 字節的的棧 func malg(stacksize int32) *g { newg := new(g) if stacksize &gt;= 0 { // 將 stacksize 舍入為 2 的指數,目的是為了消除 _StackSystem 對棧的影響 // 在 Linux/Darwin 上( _StackSystem == 0 )本行不改變 stacksize 的大小 stacksize = round2(_StackSystem + stacksize) systemstack(func() { newg.stack = stackalloc(uint32(stacksize)) }) newg.stackguard0 = newg.stack.lo + _StackGuard newg.stackguard1 = ^uintptr(0) } return newg } ``` `stackguard0`不出所料的被設置為了`stack.lo + _StackGuard`,而`stackguard1`則為`~0`。 而執行棧本身是通過`stackalloc`來進行分配。 ## 6.7.4 執行棧的分配 前面已經提到棧可能從兩個不同的位置被分配:小棧和大棧。小棧指大小為 2K/4K/8K/16K 的棧,大棧則是更大的棧。`stackalloc`基本上也就是在權衡應該從哪里分配出一個執行棧,返回所在棧的低位和高位。 當然,高低位的確立很簡單,因為我們已經知道了需要棧的大小,那么只需要知道分配好的棧的起始位置在哪兒就夠了, 即指針`v`: ``` //go:systemstack func stackalloc(n uint32) stack { thisg := getg() ... // 小棧由自由表分配器分配有固定大小。 // 如果我們需要更大尺寸的棧,我們將重新分配專用 span。 var v unsafe.Pointer // 檢查是否從緩存分配 if n &lt; _FixedStack&lt;&lt;_NumStackOrders &amp;&amp; n &lt; _StackCacheSize { ... // 小棧分配 } else { ... // 大棧分配 } ... return stack{uintptr(v), uintptr(v) + uintptr(n)} } ``` ### 小棧分配 對于大小較小的棧可以從 stackpool 或者 stackcache 中進行分配,這取決于 當產生棧分配時,Goroutine 所在的 m 是否具有 mcache (`m.mcache`)或者是否發生搶占(`m.preemptoff`): ``` // 計算對應的 mSpanList order := uint8(0) n2 := n for n2 &gt; _FixedStack { order++ n2 &gt;&gt;= 1 } var x gclinkptr c := thisg.m.mcache // 決定是否從 stackpool 中分配 if c == nil || thisg.m.preemptoff != "" { // c == nil 可能發生在 exitsyscall 或 procresize 時 lock(&amp;stackpool[order].item.mu) x = stackpoolalloc(order) unlock(&amp;stackpool[order].item.mu) } else { // 從對應鏈表提取可復用的空間 x = c.stackcache[order].list if x.ptr() == nil { // 提取失敗,擴容再重試 stackcacherefill(c, order) x = c.stackcache[order].list } c.stackcache[order].list = x.ptr().next c.stackcache[order].size -= uintptr(n) } v = unsafe.Pointer(x) // 最終取得 stack ``` 如果沒多的緩存,則向內部填充更多的緩存: ``` //go:systemstack func stackcacherefill(c *mcache, order uint8) { ... // 從全局緩存中獲取一些 stack // 獲取所允許的容量的一半來防止 thrashing var list gclinkptr var size uintptr lock(&amp;stackpool[order].item.mu) for size &lt; _StackCacheSize/2 { x := stackpoolalloc(order) x.ptr().next = list list = x size += _FixedStack &lt;&lt; order } unlock(&amp;stackpool[order].item.mu) c.stackcache[order].list = list c.stackcache[order].size = size } ``` 最終落實到`stackpoolalloc`上: ``` // 從空閑池中分配一個棧,必須在持有 stackpool[order].item.mu 下調用 func stackpoolalloc(order uint8) gclinkptr { list := &amp;stackpool[order].item.span s := list.first // 鏈表頭 if s == nil { // 緩存已空,從 mheap 上進行分配 s = mheap_.allocManual(_StackCacheSize&gt;&gt;_PageShift, &amp;memstats.stacks_inuse) ... s.elemsize = _FixedStack &lt;&lt; order for i := uintptr(0); i &lt; _StackCacheSize; i += s.elemsize { x := gclinkptr(s.base() + i) x.ptr().next = s.manualFreeList s.manualFreeList = x } list.insert(s) } x := s.manualFreeList ... s.manualFreeList = x.ptr().next s.allocCount++ if s.manualFreeList.ptr() == nil { // s 中所有的棧都被分配了 list.remove(s) } return x } ``` ### 大棧分配 大空間從`stackLarge`進行分配: ``` var s *mspan npage := uintptr(n) &gt;&gt; _PageShift log2npage := stacklog2(npage) // 嘗試從 stackLarge 緩存中獲取堆棧。 lock(&amp;stackLarge.lock) if !stackLarge.free[log2npage].isEmpty() { s = stackLarge.free[log2npage].first stackLarge.free[log2npage].remove(s) } unlock(&amp;stackLarge.lock) if s == nil { // 如果無法從緩存中獲取,則從堆中分配一個新的棧 s = mheap_.allocManual(npage, &amp;memstats.stacks_inuse) ... s.elemsize = uintptr(n) } v = unsafe.Pointer(s.base()) ``` ### 堆上分配 無論是小棧分配還是大棧的分配,在分配失敗時都會從`mheap`上分配重新分配新的緩存,使用`allocManual`: ``` //go:systemstack func (h *mheap) allocManual(npage uintptr, stat *uint64) *mspan { lock(&amp;h.lock) s := h.allocSpanLocked(npage, stat) if s != nil { s.state = mSpanManual s.manualFreeList = 0 s.allocCount = 0 s.spanclass = 0 s.nelems = 0 s.elemsize = 0 s.limit = s.base() + s.npages&lt;&lt;_PageShift ... } // This unlock acts as a release barrier. See mheap.alloc_m. unlock(&amp;h.lock) return s } ``` 其中的`allocSpanLocked`: ``` func (h *mheap) allocSpanLocked(npage uintptr, stat *uint64) *mspan { t := h.free.find(npage) // 第一次從 mheap 的緩存中尋找 if t.valid() { goto HaveSpan } if !h.grow(npage) { // 第一次沒找到,嘗試對堆進行擴充 return nil } t = h.free.find(npage) // 第二次從 mheap 緩存中尋找 if t.valid() { goto HaveSpan } throw("grew heap, but no adequate free span found") HaveSpan: s := t.span() ... return s } ``` ## 6.7.5 執行棧的伸縮 早年的 Go 運行時使用**分段棧**的機制,即當一個 Goroutine 的執行棧溢出時, 棧的擴張操作是在另一個棧上進行的,這兩個棧彼此沒有連續。 這種設計的缺陷很容易破壞緩存的局部性原理,從而降低程序的運行時性能。 因此現在 Go 運行時開始使用**連續棧**機制,當一個執行棧發生溢出時, 新建一個兩倍于原棧大小的新棧,再將原棧整個拷貝到新棧上。 從而整個棧總是連續的。棧的拷貝并非想象中的那樣簡單,因為一個棧上可能保留指向被拷貝棧的指針, 從而當棧發生拷貝后,這個指針可能還指向原棧,從而造成錯誤。 此外,Goroutine 上原本的`gobuf`也需要被更新,這也是使用連續棧的難點之一。 ### 分段標記 分段標記是編譯器的機制,涉及棧幀大小的計算。這個過程比較復雜,我們暫時假設編譯器已經計算好了棧幀的大小, 這時,編譯的預處理階段,會為沒有標記為`go:nosplit`的函數插入棧的分段檢查: ``` // cmd/internal/obj/x86/obj6.go func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { ... p := cursym.Func.Text autoffset := int32(p.To.Offset) // 棧幀大小 // 一些額外的棧幀大小計算 ... if !cursym.Func.Text.From.Sym.NoSplit() { p = stacksplit(ctxt, cursym, p, newprog, autoffset, int32(textarg)) // 觸發分段檢查 } ... } ``` 與處理階段將棧幀大小傳入`stacksplit`,用于針對不同大小的棧進行不同的分段檢查, 具體的代碼相當繁瑣,這里直接給出的是匯編的偽代碼: ``` func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgAlloc, framesize int32, textarg int32) *obj.Prog { ... var q1 *obj.Prog if framesize &lt;= objabi.StackSmall { // 小棧: SP &lt;= stackguard,直接比較 SP 和 stackguard // CMPQ SP, stackguard ... } else if framesize &lt;= objabi.StackBig { // 大棧: SP-framesize &lt;= stackguard-StackSmall // LEAQ -xxx(SP), AX // CMPQ AX, stackguard ... } else { // 更大的棧需要防止 wraparound // 如果 SP 接近于零: // SP-stackguard+StackGuard &lt;= framesize + (StackGuard-StackSmall) // 兩端的 +StackGuard 是為了保證左側大于零。 // SP 允許位于 stackguard 下面一點點 // // 搶占設置了 stackguard 為 StackPreempt,一個大到能夠打破上面的數學計算的值, // 因此必須顯式的進行檢查: // MOVQ stackguard, CX // CMPQ CX, $StackPreempt // JEQ label-of-call-to-morestack // LEAQ StackGuard(SP), AX // SUBQ CX, AX // CMPQ AX, $(framesize+(StackGuard-StackSmall)) ... } ... // 函數的尾聲 morestack := "runtime.morestack" switch { case cursym.CFunc(): morestack = "runtime.morestackc" // morestackc 會 panic,因為此時是系統棧上的 C 函數 case !cursym.Func.Text.From.Sym.NeedCtxt(): morestack = "runtime.morestack_noctxt" } call.To.Sym = ctxt.Lookup(morestack) ... return jls } ``` 總而言之,沒有被`go:nosplit`標記的函數的序言部分會插入分段檢查,從而在發生棧溢出的情況下, 觸發`runtime.morestack`調用,如果函數不需要`ctxt`,則會調用`runtime.morestack_noctxt`從而拋棄`ctxt`再調用`morestack`: ``` TEXT runtime·morestack_noctxt(SB),NOSPLIT,$0 MOVL $0, DX JMP runtime·morestack(SB) ``` ### 棧的擴張 用戶棧的擴張發生在 morestack 處,該函數此前會檢查該調用是否正確的在用戶棧上調用(因此 g0 棧和信號棧 不能發生此調用)。而后將`morebuf`設置為 f 的調用方,并將 G 的執行棧設置為 f 的 ctxt, 從而在 g0 上調用`newstack`。 ``` TEXT runtime·morestack(SB),NOSPLIT,$0-0 // 無法增長調度器的棧(m-&gt;g0) get_tls(CX) MOVQ g(CX), BX MOVQ g_m(BX), BX MOVQ m_g0(BX), SI CMPQ g(CX), SI JNE 3(PC) CALL runtime·badmorestackg0(SB) CALL runtime·abort(SB) // 無法增長信號棧 (m-&gt;gsignal) MOVQ m_gsignal(BX), SI CMPQ g(CX), SI JNE 3(PC) CALL runtime·badmorestackgsignal(SB) CALL runtime·abort(SB) // 從 f 調用 // 將 m-&gt;morebuf 設置為 f 的調用方 MOVQ 8(SP), AX // f 的調用方 PC MOVQ AX, (m_morebuf+gobuf_pc)(BX) LEAQ 16(SP), AX // f 的調用方 SP MOVQ AX, (m_morebuf+gobuf_sp)(BX) get_tls(CX) MOVQ g(CX), SI MOVQ SI, (m_morebuf+gobuf_g)(BX) // 將 g-&gt;sched 設置為 f 的 context MOVQ 0(SP), AX // f 的 PC MOVQ AX, (g_sched+gobuf_pc)(SI) MOVQ SI, (g_sched+gobuf_g)(SI) LEAQ 8(SP), AX // f 的 SP MOVQ AX, (g_sched+gobuf_sp)(SI) MOVQ BP, (g_sched+gobuf_bp)(SI) MOVQ DX, (g_sched+gobuf_ctxt)(SI) // 在 m-&gt;g0 棧上調用 newstack. MOVQ m_g0(BX), BX MOVQ BX, g(CX) MOVQ (g_sched+gobuf_sp)(BX), SP CALL runtime·newstack(SB) CALL runtime·abort(SB) // 如果 newstack 返回則崩潰 RET ``` `newstack`在前半部分承擔了對 Goroutine 進行搶占的任務(見[6.8 協作與搶占](https://golang.design/under-the-hood/zh-cn/part2runtime/ch06sched/preemption)), 而在后半部分則是真正的棧擴張。 ``` //go:nowritebarrierrec func newstack() { thisg := getg() ... gp := thisg.m.curg ... morebuf := thisg.m.morebuf thisg.m.morebuf.pc = 0 thisg.m.morebuf.lr = 0 thisg.m.morebuf.sp = 0 thisg.m.morebuf.g = 0 ... sp := gp.sched.sp if sys.ArchFamily == sys.AMD64 || sys.ArchFamily == sys.I386 || sys.ArchFamily == sys.WASM { // 到 morestack 的調用會消耗一個字 sp -= sys.PtrSize } ... // 分配一個更大的段,并對棧進行移動 oldsize := gp.stack.hi - gp.stack.lo newsize := oldsize * 2 // 兩倍于原來的大小 // 需要的棧太大,直接溢出 if newsize &gt; maxstacksize { print("runtime: goroutine stack exceeds ", maxstacksize, "-byte limit\n") throw("stack overflow") } // goroutine 必須是正在執行過程中才來調用 newstack // 所以這個狀態一定是 Grunning 或 Gscanrunning casgstatus(gp, _Grunning, _Gcopystack) // 因為 gp 處于 Gcopystack 狀態,當我們對棧進行復制時并發 GC 不會掃描此棧 copystack(gp, newsize, true) ... casgstatus(gp, _Gcopystack, _Grunning) gogo(&amp;gp.sched) // 繼續執行 } ``` ### 棧的拷貝 前面我們已經提到了,棧拷貝的其中一個難點就是 Go 中棧上的變量會包含自己的地址, 當我們拷貝了一個指向原棧的指針時,拷貝后的指針會變為無效指針。 不難發現,只有棧上分配的指針才能指向棧上的地址,否則這個指針指向的對象會重新在堆中進行分配(逃逸)。 ``` func copystack(gp *g, newsize uintptr, sync bool) { ... old := gp.stack ... used := old.hi - gp.sched.sp // 分配新的棧 new := stackalloc(uint32(newsize)) if stackPoisonCopy != 0 { fillstack(new, 0xfd) } ... // 計算調整的幅度 var adjinfo adjustinfo adjinfo.old = old adjinfo.delta = new.hi - old.hi // 調整 sudogs, 必要時與 channel 操作同步 ncopy := used if sync { adjustsudogs(gp, &amp;adjinfo) } else { // sudogs can point in to the stack. During concurrent // shrinking, these areas may be written to. Find the // highest such pointer so we can handle everything // there and below carefully. (This shouldn't be far // from the bottom of the stack, so there's little // cost in handling everything below it carefully.) adjinfo.sghi = findsghi(gp, old) // Synchronize with channel ops and copy the part of // the stack they may interact with. ncopy -= syncadjustsudogs(gp, used, &amp;adjinfo) } // 將原來的棧的內容復制到新的位置 memmove(unsafe.Pointer(new.hi-ncopy), unsafe.Pointer(old.hi-ncopy), ncopy) // Adjust remaining structures that have pointers into stacks. // We have to do most of these before we traceback the new // stack because gentraceback uses them. adjustctxt(gp, &amp;adjinfo) adjustdefers(gp, &amp;adjinfo) adjustpanics(gp, &amp;adjinfo) if adjinfo.sghi != 0 { adjinfo.sghi += adjinfo.delta } // 為新棧置換出舊棧 gp.stack = new gp.stackguard0 = new.lo + _StackGuard // 注意: 可能覆蓋(clobber)一個搶占請求 gp.sched.sp = new.hi - used gp.stktopsp += adjinfo.delta // 在新棧重調整指針 gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, adjustframe, noescape(unsafe.Pointer(&amp;adjinfo)), 0) // 釋放舊棧 if stackPoisonCopy != 0 { fillstack(old, 0xfc) } stackfree(old) } func fillstack(stk stack, b byte) { for p := stk.lo; p &lt; stk.hi; p++ { *(*byte)(unsafe.Pointer(p)) = b } } func findsghi(gp *g, stk stack) uintptr { var sghi uintptr for sg := gp.waiting; sg != nil; sg = sg.waitlink { p := uintptr(sg.elem) + uintptr(sg.c.elemsize) if stk.lo &lt;= p &amp;&amp; p &lt; stk.hi &amp;&amp; p &gt; sghi { sghi = p } } return sghi } func syncadjustsudogs(gp *g, used uintptr, adjinfo *adjustinfo) uintptr { if gp.waiting == nil { return 0 } // Lock channels to prevent concurrent send/receive. // It's important that we *only* do this for async // copystack; otherwise, gp may be in the middle of // putting itself on wait queues and this would // self-deadlock. var lastc *hchan for sg := gp.waiting; sg != nil; sg = sg.waitlink { if sg.c != lastc { lock(&amp;sg.c.lock) } lastc = sg.c } // Adjust sudogs. adjustsudogs(gp, adjinfo) // Copy the part of the stack the sudogs point in to // while holding the lock to prevent races on // send/receive slots. var sgsize uintptr if adjinfo.sghi != 0 { oldBot := adjinfo.old.hi - used newBot := oldBot + adjinfo.delta sgsize = adjinfo.sghi - oldBot memmove(unsafe.Pointer(newBot), unsafe.Pointer(oldBot), sgsize) } // Unlock channels. lastc = nil for sg := gp.waiting; sg != nil; sg = sg.waitlink { if sg.c != lastc { unlock(&amp;sg.c.lock) } lastc = sg.c } return sgsize } ``` ### 棧的收縮 棧的收縮發生在 GC 時對棧進行掃描的階段: ``` //go:nowritebarrier //go:systemstack func scanstack(gp *g, gcw *gcWork) { ... // _Grunnable, _Gsyscall, _Gwaiting 才會發生 // 如果棧使用不多,則進行棧收縮 shrinkstack(gp) ... } func shrinkstack(gp *g) { ... oldsize := gp.stack.hi - gp.stack.lo newsize := oldsize / 2 // 當收縮后的大小小于最小的棧的大小時,不再進行收縮 if newsize &lt; _FixedStack { return } // 計算當前正在使用的棧數量,如果 gp 使用的當前棧少于四分之一,則對棧進行收縮。 // 當前使用的棧包括到 SP 的所有內容以及棧保護空間,以確保有 nosplit 功能的空間。 avail := gp.stack.hi - gp.stack.lo if used := gp.stack.hi - gp.sched.sp + _StackLimit; used &gt;= avail/4 { return } // 在系統調用期間無法對棧進行拷貝 // 因為系統調用可能包含指向棧的指針 if gp.syscallsp != 0 { return } if sys.GoosWindows != 0 &amp;&amp; gp.m != nil &amp;&amp; gp.m.libcallsp != 0 { return } ... // 將舊棧拷貝到新收縮后的棧上 copystack(gp, newsize, false) } ``` 可以看到,如果一個棧僅被使用了四分之一,則會觸發棧的收縮,收縮后的大小是原來棧大小的一半。
                  <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>

                              哎呀哎呀视频在线观看