goroutine棧的申請(qǐng)與釋放

最新版本查看 https://blog.haohtml.com/archives/30403

當(dāng)我們執(zhí)行一個(gè) go func() 語句的時(shí)候,runtime 會(huì)通過調(diào)用 newproc() 函數(shù)來創(chuàng)建G。而內(nèi)部真正創(chuàng)建創(chuàng)建G的函數(shù)為 newproc1(),在沒有可以復(fù)用的G的情況下,會(huì)通過 newg = malg(_StackMin) 語句創(chuàng)建一個(gè)包含stack的G。

// Allocate a new g, with a stack big enough for stacksize bytes.
func malg(stacksize int32) *g {
    newg := new(g)
    if stacksize >= 0 {
        stacksize = round2(_StackSystem + stacksize)
        systemstack(func() {
            newg.stack = stackalloc(uint32(stacksize))
        })
        newg.stackguard0 = newg.stack.lo + _StackGuard
        newg.stackguard1 = ^uintptr(0)
        // Clear the bottom word of the stack. We record g
        // there on gsignal stack during VDSO on ARM and ARM64.
        *(*uintptr)(unsafe.Pointer(newg.stack.lo)) = 0
    }
    return newg
}

對(duì)新創(chuàng)建的g,需要通過調(diào)用 stackalloc() 函數(shù)為其分配 stacksize 大小stack,那么分配操作它又是如何工作的呢?

stack的申請(qǐng)

根據(jù)申請(qǐng)stack的大小,又分為兩種情況,一種是 small stack,另一種是 large stack,兩者采用不同的申請(qǐng)策略。主要涉及了內(nèi)存申請(qǐng)策略,如果對(duì)golang 的內(nèi)存管理比較了解的話,這塊理解起來就顯的太過于簡(jiǎn)單了。建議先閱讀一下這篇文章《Golang 內(nèi)存組件之mspan、mcache、mcentral 和 mheap 數(shù)據(jù)結(jié)構(gòu)》。

func stackalloc(n uint32) stack {
    ...

    var v unsafe.Pointer
    if n < _FixedStack<<_NumStackOrders && n < _StackCacheSize {
        order := uint8(0)
        n2 := n
        for n2 > _FixedStack {
            order++
            n2 >>= 1
        }
        var x gclinkptr
        if stackNoCache != 0 || thisg.m.p == 0 || thisg.m.preemptoff != "" {
            // thisg.m.p == 0 can happen in the guts of exitsyscall
            // or procresize. Just get a stack from the global pool.
            // Also don't touch stackcache during gc
            // as it's flushed concurrently.
            lock(&stackpool[order].item.mu)
            x = stackpoolalloc(order)
            unlock(&stackpool[order].item.mu)
        } else {
            c := thisg.m.p.ptr().mcache
            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)
    } else {
        ...
    }
    return stack{uintptr(v), uintptr(v) + uintptr(n)}
}

對(duì)于small stack,會(huì)直接通過從G綁定的P中的 mcache 字段申請(qǐng),這個(gè)字段可以理解為內(nèi)存資源中心,里面包含有多種不同規(guī)格大小的內(nèi)存塊,根據(jù)申請(qǐng)大小找到一個(gè)可以滿足其大小的最小規(guī)格的內(nèi)存區(qū)域。

這里重點(diǎn)關(guān)注下 stackcacherefill() 函數(shù)。

func stackalloc(n uint32) stack {
    ...

    var v unsafe.Pointer
    if n < _FixedStack<<_NumStackOrders && n < _StackCacheSize {
        ...
    } else {
        var s *mspan
        npage := uintptr(n) >> _PageShift
        log2npage := stacklog2(npage)

        // Try to get a stack from the large stack cache.
        lock(&stackLarge.lock)
        if !stackLarge.free[log2npage].isEmpty() {
            s = stackLarge.free[log2npage].first
            stackLarge.free[log2npage].remove(s)
        }
        unlock(&stackLarge.lock)

        lockWithRankMayAcquire(&mheap_.lock, lockRankMheap)

        if s == nil {
            // Allocate a new stack from the heap.
            s = mheap_.allocManual(npage, spanAllocStack)
            if s == nil {
                throw("out of memory")
            }
            osStackAlloc(s)
            s.elemsize = uintptr(n)
        }
        v = unsafe.Pointer(s.base())
    }
    ...
    return stack{uintptr(v), uintptr(v) + uintptr(n)}
}

對(duì)于 large stack 而言,先從 stackLarge 全局對(duì)象 large stack spans 池中查找,如果未找到的話,則調(diào)用 osStackAlloc()mheap_.allocManual() 函數(shù)直接從 heap 中直接申請(qǐng)并注冊(cè)這塊內(nèi)存。

最后根據(jù)找到內(nèi)存的起始地址(低地址)和 申請(qǐng)大小n,返回 stack 結(jié)構(gòu)體。

stack的釋放

對(duì)于stack的釋放,實(shí)現(xiàn)函數(shù)為 stackfree()。

在資源申請(qǐng)時(shí)根據(jù)其申請(qǐng)大小分為兩種情況,同樣對(duì)于stack的釋放也是一樣。對(duì)于small stack 來講,從哪里申請(qǐng)的,用完以后再放回去即可。操作對(duì)象仍是 P下面的 mcache 字段。

這里重點(diǎn)關(guān)注下 stackcacherelease() 函數(shù)。

對(duì)于 large stack 來講,則將再放在 stackLarge 全局變量里,以便可以復(fù)用,避免重復(fù)申請(qǐng)產(chǎn)生的開銷;但如果當(dāng)前GC正處于清掃階段,則直接調(diào)用 osStackFree()mheap_.freeManual() 來釋放內(nèi)存。

對(duì)于g中stack的釋放觸發(fā)條件有:

  • 當(dāng)一個(gè)g運(yùn)行結(jié)束的時(shí)候,可能會(huì)釋放stack(只是有可能)參考:https://blog.haohtml.com/archives/23437;
  • GC期間會(huì)清理所有包含stack的g,直接釋放完所有g(shù)的stack, 然后再將這些g放在不包含stack的 sched.gFree.noStack 列表中,參考:https://blog.haohtml.com/archives/27003
  • 其它
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容