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

GO語(yǔ)言中defer實(shí)現(xiàn)原理的示例詳解

 更新時(shí)間:2023年02月24日 11:31:33   作者:阿兵云原生  
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中defer實(shí)現(xiàn)原理的相關(guān)資料,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下

GO 中 defer的實(shí)現(xiàn)原理

我們來(lái)回顧一下上次的分享,分享了關(guān)于 通道的一些知識(shí)點(diǎn)

  • 分享了 GO 中通道是什么
  • 通道的底層數(shù)據(jù)結(jié)構(gòu)詳細(xì)解析
  • 通道在GO源碼中是如何實(shí)現(xiàn)的
  • Chan 讀寫(xiě)的基本原理
  • 關(guān)閉通道會(huì)出現(xiàn)哪些異常,panic
  • select 的簡(jiǎn)單應(yīng)用

要是對(duì) chan 通道還有點(diǎn)興趣的話(huà),歡迎查看文章 GO 中 Chan 實(shí)現(xiàn)原理分享

defer 是什么

咱們一起來(lái)看看 defer 是個(gè)啥

是 GO 中的一個(gè)關(guān)鍵字

這個(gè)關(guān)鍵字,我們一般用在釋放資源,在 return 前會(huì)調(diào)用他

如果程序中有多個(gè) defer ,defer 的調(diào)用順序是按照類(lèi)似的方式,后進(jìn)先出 LIFO的 ,這里順便寫(xiě)一下

遵循后進(jìn)先出原則

后進(jìn)入棧的,先出棧

先進(jìn)入棧的,后出棧

隊(duì)列

遵循先進(jìn)先出 , 我們就可以想象一個(gè)單向的管道,從左邊進(jìn),右邊出

先進(jìn)來(lái),先出去

后進(jìn)來(lái),后出去,不準(zhǔn)插隊(duì)

defer 實(shí)現(xiàn)原理

咱們先拋出一個(gè)結(jié)論,先心里有點(diǎn)底:

代碼中聲明 defer的位置,編譯的時(shí)候會(huì)插入一個(gè)函數(shù)叫做 deferproc ,在該defer所在的函數(shù)前插入一個(gè)返回的函數(shù),不是return 哦,是deferreturn

具體的 defer 的實(shí)現(xiàn)原理是咋樣的,我們還是一樣的,來(lái)看看 defer的底層數(shù)據(jù)結(jié)構(gòu)是啥樣的 ,

src/runtime/runtime2.gotype _defer struct {結(jié)構(gòu)

// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer and deferProcStack
// This struct must match the code in cmd/compile/internal/gc/reflect.go:deferstruct
// and cmd/compile/internal/gc/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap.
// All defers are logically part of the stack, so write barriers to
// initialize them are not required. All defers must be manually scanned,
// and for heap defers, marked.
type _defer struct {
   siz     int32 // includes both arguments and results
   started bool
   heap    bool
   // openDefer indicates that this _defer is for a frame with open-coded
   // defers. We have only one defer record for the entire frame (which may
   // currently have 0, 1, or more defers active).
   openDefer bool
   sp        uintptr  // sp at time of defer
   pc        uintptr  // pc at time of defer
   fn        *funcval // can be nil for open-coded defers
   _panic    *_panic  // panic that is running defer
   link      *_defer

   // If openDefer is true, the fields below record values about the stack
   // frame and associated function that has the open-coded defer(s). sp
   // above will be the sp for the frame, and pc will be address of the
   // deferreturn call in the function.
   fd   unsafe.Pointer // funcdata for the function associated with the frame
   varp uintptr        // value of varp for the stack frame
   // framepc is the current pc associated with the stack frame. Together,
   // with sp above (which is the sp associated with the stack frame),
   // framepc/sp can be used as pc/sp pair to continue a stack trace via
   // gentraceback().
   framepc uintptr
}

_defer 持有延遲調(diào)用列表中的一個(gè)條目 ,我們來(lái)看看上述數(shù)據(jù)結(jié)構(gòu)的參數(shù)都是啥意思

tag說(shuō)明
sizdefer函數(shù)的參數(shù)和結(jié)果的內(nèi)存大小
fn需要被延遲執(zhí)行的函數(shù)
_panicdefer 的 panic 結(jié)構(gòu)體
link同一個(gè)協(xié)程里面的defer 延遲函數(shù),會(huì)通過(guò)該指針連接在一起
heap是否分配在堆上面
openDefer是否經(jīng)過(guò)開(kāi)放編碼優(yōu)化
sp棧指針(一般會(huì)對(duì)應(yīng)到匯編)
pc程序計(jì)數(shù)器

defer 關(guān)鍵字后面必須是跟函數(shù),這一點(diǎn)咱們要記住哦

通過(guò)上述參數(shù)的描述,我們可以知道,defer的數(shù)據(jù)結(jié)構(gòu)和函數(shù)類(lèi)似,也是有如下三個(gè)參數(shù):

  • 棧指針 SP
  • 程序計(jì)數(shù)器 PC
  • 函數(shù)的地址

可是我們是不是也發(fā)現(xiàn)了,成員里面還有一個(gè)link,同一個(gè)協(xié)程里面的defer 延遲函數(shù),會(huì)通過(guò)該指針連接在一起

這個(gè)link指針,是指向的一個(gè)defer單鏈表的頭,每次咱們聲明一個(gè)defer的時(shí)候,就會(huì)將該defer的數(shù)據(jù)插入到這個(gè)單鏈表頭部的位置,

那么,執(zhí)行defer的時(shí)候,我們是不是就能猜到defer 是咋取得了不?

前面有說(shuō)到defer是后進(jìn)先出的,這里當(dāng)然也是遵循這個(gè)道理,取defer進(jìn)行執(zhí)行的時(shí)候,是從單鏈表的頭開(kāi)始去取的。

咱們來(lái)畫(huà)個(gè)圖形象一點(diǎn)

在協(xié)程A中聲明2個(gè)defer,先聲明 defer test1()

再聲明 defer test2()

可以看出后聲明的defer會(huì)插入到單鏈表的頭,先聲明的defer被排到后面去了

咱們?nèi)〉臅r(shí)候也是一直取頭下來(lái)執(zhí)行,直到單鏈表為空。

咱一起來(lái)看看defer 的具體實(shí)現(xiàn)

源碼文件在 src/runtime/panic.go 中,查看 函數(shù) deferproc

// Create a new deferred function fn with siz bytes of arguments.
// The compiler turns a defer statement into a call to this.
//go:nosplit
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
   gp := getg()
   if gp.m.curg != gp {
      // go code on the system stack can't defer
      throw("defer on system stack")
   }

   // the arguments of fn are in a perilous state. The stack map
   // for deferproc does not describe them. So we can't let garbage
   // collection or stack copying trigger until we've copied them out
   // to somewhere safe. The memmove below does that.
   // Until the copy completes, we can only call nosplit routines.
   sp := getcallersp()
   argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
   callerpc := getcallerpc()

   d := newdefer(siz)
   if d._panic != nil {
      throw("deferproc: d.panic != nil after newdefer")
   }
   d.link = gp._defer
   gp._defer = d
   d.fn = fn
   d.pc = callerpc
   d.sp = sp
   switch siz {
   case 0:
      // Do nothing.
   case sys.PtrSize:
      *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
   default:
      memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
   }

   // deferproc returns 0 normally.
   // a deferred func that stops a panic
   // makes the deferproc return 1.
   // the code the compiler generates always
   // checks the return value and jumps to the
   // end of the function if deferproc returns != 0.
   return0()
   // No code can go here - the C return register has
   // been set and must not be clobbered.
}

deferproc 的作用是

創(chuàng)建一個(gè)新的遞延函數(shù) fn,參數(shù)為 siz 字節(jié),編譯器將一個(gè)延遲語(yǔ)句轉(zhuǎn)換為對(duì)this的調(diào)用

getcallersp()

得到deferproc之前的rsp寄存器的值,實(shí)現(xiàn)的方式所有平臺(tái)都是一樣的

//go:noescape
func getcallersp() uintptr // implemented as an intrinsic on all platforms

callerpc := getcallerpc()

此處得到 rsp之后,存儲(chǔ)在 callerpc 中 , 此處是為了調(diào)用 deferproc 的下一條指令

d := newdefer(siz)

d := newdefer(siz) 新建一個(gè)defer 的結(jié)構(gòu),后續(xù)的代碼是在給defer 這個(gè)結(jié)構(gòu)的成員賦值

咱看看 deferproc 的大體流程

  • 獲取 deferproc之前的rsp寄存器的值
  • 使用newdefer 分配一個(gè) _defer 結(jié)構(gòu)體對(duì)象,并且將他放到當(dāng)前的 _defer 鏈表的頭
  • 初始化_defer 的相關(guān)成員參數(shù)
  • return0

來(lái)我們看看 newdefer的源碼

源碼文件在 src/runtime/panic.go 中,查看函數(shù)newdefer

// Allocate a Defer, usually using per-P pool.
// Each defer must be released with freedefer.  The defer is not
// added to any defer chain yet.
//
// This must not grow the stack because there may be a frame without
// stack map information when this is called.
//
//go:nosplit
func newdefer(siz int32) *_defer {
	var d *_defer
	sc := deferclass(uintptr(siz))
	gp := getg()
	if sc < uintptr(len(p{}.deferpool)) {
		pp := gp.m.p.ptr()
		if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {
			// Take the slow path on the system stack so
			// we don't grow newdefer's stack.
			systemstack(func() {
				lock(&sched.deferlock)
				for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
					d := sched.deferpool[sc]
					sched.deferpool[sc] = d.link
					d.link = nil
					pp.deferpool[sc] = append(pp.deferpool[sc], d)
				}
				unlock(&sched.deferlock)
			})
		}
		if n := len(pp.deferpool[sc]); n > 0 {
			d = pp.deferpool[sc][n-1]
			pp.deferpool[sc][n-1] = nil
			pp.deferpool[sc] = pp.deferpool[sc][:n-1]
		}
	}
	if d == nil {
		// Allocate new defer+args.
		systemstack(func() {
			total := roundupsize(totaldefersize(uintptr(siz)))
			d = (*_defer)(mallocgc(total, deferType, true))
		})
	}
	d.siz = siz
	d.heap = true
	return d
}

newderfer 的作用:

通常使用per-P池,分配一個(gè)Defer

每個(gè)defer可以自由的釋放。當(dāng)前defer 也不會(huì)加入任何一個(gè) defer鏈條中

getg()

獲取當(dāng)前協(xié)程的結(jié)構(gòu)體指針

// getg returns the pointer to the current g.
// The compiler rewrites calls to this function into instructions
// that fetch the g directly (from TLS or from the dedicated register).
func getg() *g

pp := gp.m.p.ptr()

拿到當(dāng)前工作線(xiàn)程里面的 P

然后拿到 從全局的對(duì)象池子中拿一部分對(duì)象給到P的池子里面

for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
					d := sched.deferpool[sc]
					sched.deferpool[sc] = d.link
					d.link = nil
					pp.deferpool[sc] = append(pp.deferpool[sc], d)
				}

點(diǎn)進(jìn)去看池子的數(shù)據(jù)結(jié)構(gòu),其實(shí)里面的成員也就是 咱們之前說(shuō)到的 _defer指針

其中 sched.deferpool[sc] 是全局的池子,pp.deferpool[sc] 是本地的池子

mallocgc分配空間

上述操作若 d 沒(méi)有拿到值,那么就直接使用 mallocgc 重新分配,且設(shè)置好 對(duì)應(yīng)的成員 sizheap

if d == nil {
		// Allocate new defer+args.
		systemstack(func() {
			total := roundupsize(totaldefersize(uintptr(siz)))
			d = (*_defer)(mallocgc(total, deferType, true))
		})
	}
d.siz = siz
d.heap = true

mallocgc 具體實(shí)現(xiàn)在 src/runtime/malloc.go 中,若感興趣的話(huà),可以深入看看這一塊,今天咱們不重點(diǎn)說(shuō)這個(gè)函數(shù)

// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {}

最后再來(lái)看看return0

最后再來(lái)看看 deferproc 函數(shù)中的 結(jié)果返回return0()

// return0 is a stub used to return 0 from deferproc.
// It is called at the very end of deferproc to signal
// the calling Go function that it should not jump
// to deferreturn.
// in asm_*.s
func return0()

return0 是用于從deferproc返回0的存根

它在deferproc函數(shù)的最后被調(diào)用,用來(lái)通知調(diào)用Go的函數(shù)它不應(yīng)該跳轉(zhuǎn)到deferreturn

在正常情況下 return0 正常返回 0

可是異常情況下 return0 函數(shù)會(huì)返回 1,此時(shí)GO 就會(huì)跳轉(zhuǎn)到執(zhí)行 deferreturn

簡(jiǎn)單說(shuō)下 deferreturn

deferreturn的作用就是情況defer里面的鏈表,歸還相應(yīng)的緩沖區(qū),或者把對(duì)應(yīng)的空間讓GC回收調(diào)

GO 中 defer 的規(guī)則

上面分析了GO 中defer 的實(shí)現(xiàn)原理之后,咱們現(xiàn)在來(lái)了解一下 GO 中應(yīng)用defer 是需要遵守 3 個(gè)規(guī)則的,咱們來(lái)列一下:

  • defer后面跟的函數(shù),叫延遲函數(shù),函數(shù)中的參數(shù)在defer語(yǔ)句聲明的時(shí)候,就已經(jīng)確定下來(lái)了
  • 延遲函數(shù)的執(zhí)行時(shí)按照后進(jìn)先出來(lái)的,文章前面也多次說(shuō)到過(guò),這個(gè)印象應(yīng)該很深刻吧,先出現(xiàn)的defer后執(zhí)行,后出現(xiàn)的defer先執(zhí)行
  • 延遲函數(shù)可能會(huì)影響到整個(gè)函數(shù)的返回值

咱們還是要來(lái)解釋一下的,上面第 2 點(diǎn),應(yīng)該都好理解,上面的圖也表明了 執(zhí)行順序

第一點(diǎn)咱們來(lái)寫(xiě)個(gè)小DEMO

延遲函數(shù)中的參數(shù)在defer語(yǔ)句聲明的時(shí)候,就已經(jīng)確定下來(lái)了

func main() {
   num := 1
   defer fmt.Println(num)

   num++

   return
}

別猜了,運(yùn)行結(jié)果是 1,小伙伴們可以將代碼拷貝下來(lái),自己運(yùn)行一波

第三點(diǎn)也來(lái)一個(gè)DEMO

延遲函數(shù)可能會(huì)影響到整個(gè)函數(shù)的返回值

func test3() (res int) {
   defer func() {
      res++
   }()

   return 1
}
func main() {

   fmt.Println(test3())

   return
}

上述代碼,我們?cè)?test3函數(shù)中的返回值,我們提前命名好了,本來(lái)應(yīng)該是返回結(jié)果為 1

可是在return 這里,執(zhí)行順序這樣的

res = 1

res++

因此,結(jié)果就是 2

總結(jié)

  • 分享了defer是什么
  • 簡(jiǎn)單示意了棧和隊(duì)列
  • defer的數(shù)據(jù)結(jié)構(gòu)和實(shí)現(xiàn)原理,具體的源碼展示
  • GO中defer的 3 條規(guī)則

以上就是GO語(yǔ)言中defer實(shí)現(xiàn)原理的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于GO語(yǔ)言defer的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 用Go+Vue.js快速搭建一個(gè)Web應(yīng)用(初級(jí)demo)

    用Go+Vue.js快速搭建一個(gè)Web應(yīng)用(初級(jí)demo)

    這篇文章主要介紹了用Go+Vue.js快速搭建一個(gè)Web應(yīng)用(初級(jí)demo),本文給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-11-11
  • golang標(biāo)準(zhǔn)庫(kù)SSH操作示例詳解

    golang標(biāo)準(zhǔn)庫(kù)SSH操作示例詳解

    文章介紹了如何使用Golang的crypto/ssh庫(kù)實(shí)現(xiàn)SSH客戶(hù)端功能,包括連接遠(yuǎn)程服務(wù)器、執(zhí)行命令、捕獲輸出以及與os/exec標(biāo)準(zhǔn)庫(kù)的對(duì)比,本文給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2024-12-12
  • golang語(yǔ)言編碼規(guī)范的實(shí)現(xiàn)

    golang語(yǔ)言編碼規(guī)范的實(shí)現(xiàn)

    這篇文章主要介紹了golang語(yǔ)言編碼規(guī)范的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Golang 并發(fā)以及通道的使用方式

    Golang 并發(fā)以及通道的使用方式

    這篇文章主要介紹了Golang 并發(fā)以及通道的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-03-03
  • Go語(yǔ)言進(jìn)行多時(shí)區(qū)時(shí)間轉(zhuǎn)換的示例代碼

    Go語(yǔ)言進(jìn)行多時(shí)區(qū)時(shí)間轉(zhuǎn)換的示例代碼

    本文介紹了使用Go語(yǔ)言進(jìn)行多時(shí)區(qū)時(shí)間轉(zhuǎn)換,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-12-12
  • Golang?Makefile示例深入講解使用

    Golang?Makefile示例深入講解使用

    一次偶然的機(jī)會(huì),在?github?上看到有人用?Makefile,就嘗試了一下,發(fā)現(xiàn)真的非常合適,Makefile?本身就是用來(lái)描述依賴(lài)的,可讀性非常好,而且與強(qiáng)大的?shell?結(jié)合在一起,基本可以實(shí)現(xiàn)任何想要的功能
    2023-01-01
  • 詳解如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎

    詳解如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎

    規(guī)則引擎的功能可以簡(jiǎn)化為當(dāng)滿(mǎn)足一些條件時(shí)觸發(fā)一些操作,通常使用 DSL 自定義語(yǔ)法來(lái)表述,本文給大家介紹了如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下
    2024-05-05
  • Go語(yǔ)言清除文件中空行的方法

    Go語(yǔ)言清除文件中空行的方法

    這篇文章主要介紹了Go語(yǔ)言清除文件中空行的方法,實(shí)例分析了Go語(yǔ)言針對(duì)文件的操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • 一篇文章搞懂Go語(yǔ)言中的Context

    一篇文章搞懂Go語(yǔ)言中的Context

    這篇文章主要介紹了一篇文章搞懂Go語(yǔ)言中的Context,Context攜帶一個(gè)截止日期、一個(gè)取消信號(hào)和其他跨越API邊界的值。上下文的方法可以被多個(gè)gor例程同時(shí)調(diào)用
    2022-07-07
  • golang 對(duì)私有函數(shù)進(jìn)行單元測(cè)試的實(shí)例

    golang 對(duì)私有函數(shù)進(jìn)行單元測(cè)試的實(shí)例

    這篇文章主要介紹了golang 對(duì)私有函數(shù)進(jìn)行單元測(cè)試的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05

最新評(píng)論

茂名市| 康保县| 铜鼓县| 文水县| 桂平市| 呼图壁县| 宜兴市| 北流市| 兴仁县| 鹤庆县| 平乐县| 新平| 阳谷县| 神农架林区| 湖口县| 法库县| 福泉市| 信阳市| 织金县| 弋阳县| 兴文县| 达拉特旗| 齐齐哈尔市| 张家口市| 小金县| 江华| 涞源县| 呼伦贝尔市| 滦平县| 宣威市| 唐海县| 手游| 徐水县| 馆陶县| 和林格尔县| 原阳县| 宁乡县| 正镶白旗| 星座| 曲沃县| 民勤县|