欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文詳解Golang內(nèi)存管理之棧空間管理

 更新時(shí)間:2023年06月30日 11:57:17   作者:IguoChan  
這篇文章主要介紹了Golang內(nèi)存管理的??臻g管理,文章通過代碼示例介紹的非常詳細(xì),對我們學(xué)習(xí)Golang內(nèi)存管理有一定的幫助,需要的朋友跟著小編一起來學(xué)習(xí)吧

0. 簡介

前面我們分別介紹了堆空間管理的內(nèi)存分配器垃圾收集,這里我們簡單介紹一下Go中??臻g的管理。

1. 系統(tǒng)棧和Go棧

1.1 系統(tǒng)線程棧

如果我們在Linux中執(zhí)行 pthread_create 系統(tǒng)調(diào)用,進(jìn)程會啟動一個(gè)新的線程,這個(gè)棧大小一般為系統(tǒng)的默認(rèn)棧大小,比如在以下系統(tǒng)中,棧大小是8192KB,也就是8M大小。

$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 128528
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 4194304
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 515129
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

對于棧上的內(nèi)存,程序員無法直接操作,由系統(tǒng)統(tǒng)一管理,一般的函數(shù)參數(shù)、局部變量(C語言)會存儲在棧上。

1.2 Go棧

Go語言在用戶空間實(shí)現(xiàn)了一套runtime的管理系統(tǒng),其中就包括了對內(nèi)存的管理,Go的內(nèi)存也區(qū)分堆和棧,但是需要注意的是,Go棧內(nèi)存其實(shí)是從系統(tǒng)堆中分配的內(nèi)存,因?yàn)橥瑯舆\(yùn)行在用戶態(tài),Go的運(yùn)行時(shí)也沒有權(quán)限去直接操縱系統(tǒng)棧。

Go語言使用用戶態(tài)協(xié)程goroutine作為執(zhí)行的上下文,其使用的默認(rèn)棧大小比線程棧高的多,其??臻g和棧結(jié)構(gòu)也在早期幾個(gè)版本中發(fā)生過一些變化:

  • v1.0 ~ v1.1 — 最小棧內(nèi)存空間為 4KB;
  • v1.2 — 將最小棧內(nèi)存提升到了 8KB;
  • v1.3 — 使用連續(xù)棧替換之前版本的分段棧;
  • v1.4 — 將最小棧內(nèi)存降低到了 2KB;

2. 棧操作

在前面的《Golang調(diào)度器》系列我們也講過,Go語言中的執(zhí)行棧由runtime.stack,該結(jié)構(gòu)體中只包含兩段字段,分別表示棧的頂部和底部,每個(gè)棧結(jié)構(gòu)體都在[lo, hi)的范圍內(nèi):

type stack struct {
	lo uintptr
	hi uintptr
}

棧的結(jié)構(gòu)雖然非常簡單,但是想要理解 Goroutine 棧的實(shí)現(xiàn)原理,還是需要我們從編譯期間和運(yùn)行時(shí)兩個(gè)階段入手:

  • 編譯器會在編譯階段會通過cmd/internal/obj/x86.stacksplit在調(diào)用函數(shù)前插入runtime.morestack或者runtime.morestack_noctxt函數(shù);
  • 運(yùn)行時(shí)創(chuàng)建新的 Goroutine 時(shí)會在runtime.malg中調(diào)用runtime.stackalloc申請新的棧內(nèi)存,并在編譯器插入的runtime.morestack中檢查棧空間是否充足;

當(dāng)然,可以在函數(shù)頭加上//go:nosplit跳過棧溢出檢查。

2.1 棧初始化

??臻g運(yùn)行時(shí)中包含兩個(gè)重要的全局變量,分別是stackpoolstackLarge,這兩個(gè)變量分別表示全局的棧緩存和大棧緩存,前者可以分配小于 32KB 的內(nèi)存,后者用來分配大于 32KB 的??臻g:

var stackpool [_NumStackOrders]struct {
   item stackpoolItem
   _    [cpu.CacheLinePadSize - unsafe.Sizeof(stackpoolItem{})%cpu.CacheLinePadSize]byte
}
//go:notinheap
type stackpoolItem struct {
   mu   mutex
   span mSpanList
}
// Global pool of large stack spans.
var stackLarge struct {
   lock mutex
   free [heapAddrBits - pageShift]mSpanList // free lists by log_2(s.npages)
}

其初始化函數(shù)如下,從下也可以看出,Go棧的內(nèi)存都是分配在堆上的:

func stackinit() {
   if _StackCacheSize&_PageMask != 0 {
      throw("cache size must be a multiple of page size")
   }
   for i := range stackpool {
      stackpool[i].item.span.init()
      lockInit(&stackpool[i].item.mu, lockRankStackpool)
   }
   for i := range stackLarge.free {
      stackLarge.free[i].init()
      lockInit(&stackLarge.lock, lockRankStackLarge)
   }
}

2.2 棧分配

我們在這里會按照棧的大小分兩部分介紹運(yùn)行時(shí)對??臻g的分配。在 Linux 上,_FixedStack = 2048、_NumStackOrders = 4、_StackCacheSize = 32768,也就是如果申請的??臻g小于 32KB,我們會在全局棧緩存池或者線程的棧緩存中初始化內(nèi)存:

//go:systemstack
func stackalloc(n uint32) stack {
   ...
   thisg := getg()
   ...
   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 { // 不夠就調(diào)用stackcacherefill從堆上獲取
            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)}
}

如果申請的內(nèi)存空間過大,運(yùn)行時(shí)會查看runtime.stackLarge中是否有剩余的空間,如果不存在剩余空間,它也會從堆上申請新的內(nèi)存:

//go:systemstack
func stackalloc(n uint32) stack {
   ...
   thisg := getg()
   ...
   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() { // 從stackLarge拿
         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)}
}

2.3 棧擴(kuò)容

在之前我們就提過,編譯器會在cmd/internal/obj/x86.stacksplit中為函數(shù)調(diào)用插入runtime.morestack運(yùn)行時(shí)檢查,它會在幾乎所有的函數(shù)調(diào)用之前檢查當(dāng)前 Goroutine 的棧內(nèi)存是否充足,如果當(dāng)前棧需要擴(kuò)容,我們會保存一些棧的相關(guān)信息并調(diào)用runtime.newstack創(chuàng)建新的棧。

在此期間可能觸發(fā)搶占。

接下來就是分配新的棧內(nèi)存和??截?,這里就不詳細(xì)描述了。

2.4 ??s容

runtime.shrinkstack??s容時(shí)調(diào)用的函數(shù),該函數(shù)的實(shí)現(xiàn)原理非常簡單,其中大部分都是檢查是否滿足縮容前置條件的代碼,核心邏輯只有以下這幾行:

func shrinkstack(gp *g) {
	...
	oldsize := gp.stack.hi - gp.stack.lo
	newsize := oldsize / 2
	if newsize < _FixedStack {
		return
	}
	avail := gp.stack.hi - gp.stack.lo
	if used := gp.stack.hi - gp.sched.sp + _StackLimit; used >= avail/4 {
		return
	}
	copystack(gp, newsize)
}

如果要觸發(fā)棧的縮容,新棧的大小會是原始棧的一半,不過如果新棧的大小低于程序的最低限制2KB,那么縮容的過程就會停止。

運(yùn)行時(shí)只會在棧內(nèi)存使用不足1/4時(shí)進(jìn)行縮容,縮容也會調(diào)用擴(kuò)容時(shí)使用的runtime.copystack開辟新的??臻g。

以上就是一文詳解Golang內(nèi)存管理之棧空間管理的詳細(xì)內(nèi)容,更多關(guān)于Golang??臻g管理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 深入了解Go語言編譯鏈接的過程

    深入了解Go語言編譯鏈接的過程

    Go在編譯時(shí)會將interface和channel關(guān)鍵字轉(zhuǎn)換成runtime中的結(jié)構(gòu)和函數(shù)調(diào)用,所以小編覺得很有必要就Go的編譯過程理一理做個(gè)進(jìn)行總結(jié),下面就來和小編一起了解一下Go語言編譯鏈接的過程吧
    2023-08-08
  • Go語言通道之無緩沖通道與緩沖通道詳解

    Go語言通道之無緩沖通道與緩沖通道詳解

    通道是一種特殊的數(shù)據(jù)結(jié)構(gòu),可以在協(xié)程之間進(jìn)行傳遞數(shù)據(jù),從而實(shí)現(xiàn)協(xié)程之間的通信和同步,本文就來和大家講講Go語言通道中的無緩沖通道與緩沖通道吧
    2023-06-06
  • 使用go操作redis的有序集合(zset)

    使用go操作redis的有序集合(zset)

    這篇文章主要介紹了使用go操作redis的有序集合(zset),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 高效封禁:利用Go封裝功能,提升封禁操作效率

    高效封禁:利用Go封裝功能,提升封禁操作效率

    在網(wǎng)絡(luò)安全領(lǐng)域,封禁操作是一項(xiàng)重要的任務(wù),用于阻止惡意行為和保護(hù)系統(tǒng)安全,而利用Go語言封裝功能可以提升封禁操作的效率,Go語言具有高效的并發(fā)性能和簡潔的語法,使得開發(fā)者可以快速構(gòu)建高性能的封禁系統(tǒng),
    2023-10-10
  • Go語言中內(nèi)存管理逃逸分析詳解

    Go語言中內(nèi)存管理逃逸分析詳解

    所謂的逃逸分析(Escape?analysis)是指由編譯器決定內(nèi)存分配的位置嗎不需要程序員指定。本文就來和大家簡單分析一下Go語言中內(nèi)存管理逃逸吧
    2023-03-03
  • golang之?dāng)?shù)據(jù)校驗(yàn)的實(shí)現(xiàn)代碼示例

    golang之?dāng)?shù)據(jù)校驗(yàn)的實(shí)現(xiàn)代碼示例

    這篇文章主要介紹了golang之?dāng)?shù)據(jù)校檢的實(shí)現(xiàn)代碼示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Go語言開發(fā)k8s之Deployment操作解析

    Go語言開發(fā)k8s之Deployment操作解析

    這篇文章主要為大家介紹了Go語言開發(fā)k8s之Deployment操作解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • 深入理解go slice結(jié)構(gòu)

    深入理解go slice結(jié)構(gòu)

    這篇文章主要介紹了go slice結(jié)構(gòu),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2021-09-09
  • Go中過濾范型集合性能示例詳解

    Go中過濾范型集合性能示例詳解

    這篇文章主要為大家介紹了Go中過濾范型集合性能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • Go語言定時(shí)器Timer和Ticker的使用與區(qū)別

    Go語言定時(shí)器Timer和Ticker的使用與區(qū)別

    在Go語言中內(nèi)置的有兩個(gè)定時(shí)器,Timer和Ticker,本文主要介紹了Go語言定時(shí)器Timer和Ticker的使用與區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-07-07

最新評論