最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2023年06月30日 11:57:17   作者:IguoChan  
這篇文章主要介紹了Golang內(nèi)存管理的??臻g管理,文章通過代碼示例介紹的非常詳細,對我們學(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)用,進程會啟動一個新的線程,這個棧大小一般為系統(tǒng)的默認棧大小,比如在以下系統(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語言在用戶空間實現(xiàn)了一套runtime的管理系統(tǒng),其中就包括了對內(nèi)存的管理,Go的內(nèi)存也區(qū)分堆和棧,但是需要注意的是,Go棧內(nèi)存其實是從系統(tǒng)堆中分配的內(nèi)存,因為同樣運行在用戶態(tài),Go的運行時也沒有權(quán)限去直接操縱系統(tǒng)棧。

Go語言使用用戶態(tài)協(xié)程goroutine作為執(zhí)行的上下文,其使用的默認棧大小比線程棧高的多,其??臻g和棧結(jié)構(gòu)也在早期幾個版本中發(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)體中只包含兩段字段,分別表示棧的頂部和底部,每個棧結(jié)構(gòu)體都在[lo, hi)的范圍內(nèi):

type stack struct {
	lo uintptr
	hi uintptr
}

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

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

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

2.1 棧初始化

??臻g運行時中包含兩個重要的全局變量,分別是stackpoolstackLarge,這兩個變量分別表示全局的棧緩存和大棧緩存,前者可以分配小于 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 棧分配

我們在這里會按照棧的大小分兩部分介紹運行時對??臻g的分配。在 Linux 上,_FixedStack = 2048、_NumStackOrders = 4_StackCacheSize = 32768,也就是如果申請的棧空間小于 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)存空間過大,運行時會查看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 棧擴容

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

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

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

2.4 ??s容

runtime.shrinkstack??s容時調(diào)用的函數(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,那么縮容的過程就會停止。

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

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

相關(guān)文章

  • 如何避免go的map競態(tài)問題的方法

    如何避免go的map競態(tài)問題的方法

    本文主要介紹了如何避免go的map競態(tài)問題的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Go 自定義error錯誤的處理方法

    Go 自定義error錯誤的處理方法

    這篇文章主要介紹了Go 自定義error錯誤的處理方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • go?mod詳細使用教程

    go?mod詳細使用教程

    go mod是go的一個模塊管理工具,用來代替?zhèn)鹘y(tǒng)的GOPATH方案,下面這篇文章主要給大家介紹了關(guān)于go?mod詳細使用的相關(guān)資料,文中通過圖文以及實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-07-07
  • 深入淺出go依賴注入工具Wire的使用

    深入淺出go依賴注入工具Wire的使用

    但隨著項目規(guī)模的增長,組件之間的依賴關(guān)系變得復(fù)雜,手動管理可能會很繁瑣,所以本文將深入探討一個備受歡迎的?Go?語言依賴注入工具——?Wire,感興趣的可以了解下
    2023-09-09
  • Golang Http 驗證碼示例實現(xiàn)

    Golang Http 驗證碼示例實現(xiàn)

    這篇文章主要介紹了Golang Http 驗證碼示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • golang調(diào)用dll的接口三種方式小結(jié)

    golang調(diào)用dll的接口三種方式小結(jié)

    本文介紹了在Go語言中使用syscall包的不同方法加載DLL文件,包括NewLazyDLL、MustLoadDLL、LoadLibrary以及它們在調(diào)用函數(shù)和處理不同數(shù)據(jù)類型轉(zhuǎn)換中的應(yīng)用,感興趣的可以了解一下
    2025-07-07
  • Go語言中讀取命令參數(shù)的幾種方法總結(jié)

    Go語言中讀取命令參數(shù)的幾種方法總結(jié)

    部署golang項目時難免要通過命令行來設(shè)置一些參數(shù),那么在golang中如何操作命令行參數(shù)呢?那么下面這篇文章就來給大家介紹了關(guān)于Go語言中讀取命令參數(shù)的幾種方法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起看看吧。
    2017-11-11
  • Golang中匿名組合實現(xiàn)偽繼承的方法

    Golang中匿名組合實現(xiàn)偽繼承的方法

    這篇文章主要介紹了Golang中匿名組合實現(xiàn)偽繼承的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • go日志庫中的logrus

    go日志庫中的logrus

    這篇文章主要介紹了go日志庫中的logrus主要包括go日志庫logrus的安裝和使用,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • golang搭建靜態(tài)web服務(wù)器的實現(xiàn)方法

    golang搭建靜態(tài)web服務(wù)器的實現(xiàn)方法

    這篇文章主要介紹了golang搭建靜態(tài)web服務(wù)器的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08

最新評論

顺义区| 旅游| 临沧市| 祁阳县| 兴海县| 高邑县| 阿尔山市| 利津县| 岳阳市| 得荣县| 邵阳县| 崇仁县| 万盛区| 张掖市| 正蓝旗| 读书| 定结县| 双桥区| 许昌市| 饶河县| 太仆寺旗| 洱源县| 吴旗县| 子长县| 遂昌县| 佛坪县| 新昌县| 龙里县| 娱乐| 鹤壁市| 刚察县| 平和县| 浪卡子县| 繁峙县| 福安市| 拉萨市| 岫岩| 瑞昌市| 自治县| 哈巴河县| 茶陵县|