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

Golang的關(guān)鍵字defer的使用方法

 更新時(shí)間:2022年06月08日 11:56:26   作者:??談笑風(fēng)生間????  
這篇文章主要介紹了Golang的關(guān)鍵字defer的使用方法,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下

核心思想

在defer出現(xiàn)的地方插入了指令CALL runtime.deferproc,在函數(shù)返回的地方插入了CALL runtime.deferreturn。goroutine的控制結(jié)構(gòu)中,有一張表記錄defer,調(diào)用runtime.deferproc時(shí)會將需要defer的表達(dá)式記錄在表中,而在調(diào)用runtime.deferreturn的時(shí)候,則會依次從defer表中“出棧”并執(zhí)行

如果有多個(gè)defer,調(diào)用順序類似棧,越后面的defer表達(dá)式越先被調(diào)用

defer鏈

defer信息會注冊到鏈表,當(dāng)前執(zhí)行的 goroutine 持有這個(gè)鏈表的頭指針,每個(gè) goroutine 都有一個(gè)對應(yīng)的結(jié)構(gòu)體struct G,其中有一個(gè)字段指向這個(gè)defer鏈表頭

type g struct {
	// Stack parameters.
	// stack describes the actual stack memory: [stack.lo, stack.hi).
	// stackguard0 is the stack pointer compared in the Go stack growth prologue.
	// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
	// stackguard1 is the stack pointer compared in the C stack growth prologue.
	// It is stack.lo+StackGuard on g0 and gsignal stacks.
	// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
	stack       stack   // offset known to runtime/cgo
	stackguard0 uintptr // offset known to liblink
	stackguard1 uintptr // offset known to liblink

	_panic       *_panic // innermost panic - offset known to liblink
    // _defer 這個(gè)字段指向defer鏈表頭
	_defer       *_defer // innermost defer
    ...
}

新注冊的defer會添加到鏈表頭,所以感覺像是棧那樣先進(jìn)后出的調(diào)用:

源碼分析

deferproc一共有兩個(gè)參數(shù),第一個(gè)是參數(shù)和返回值的大小,第二個(gè)是指向funcval的指針

// 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
    // 獲取當(dāng)前goroutine
	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.
    // 獲取調(diào)用者指針
	sp := getcallersp()
    // 通過偏移獲得參數(shù)
	argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
	callerpc := getcallerpc()

    // 創(chuàng)建defer結(jié)構(gòu)體
	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.
}
// 以下是_defer結(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 記錄defer的參數(shù)和返回值共占多少字節(jié)
    // 會直接分配在_defer后面,在注冊時(shí)保存參數(shù),在執(zhí)行完成時(shí)拷貝到調(diào)用者參數(shù)和返回值空間
	siz     int32 // includes both arguments and results
	// started 標(biāo)記是否已經(jīng)執(zhí)行
    started bool
    // heap go1.13優(yōu)化,標(biāo)識是否為堆分配
	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 是否是open defer,通過這些信息可以找到未注冊到鏈表的defer函數(shù)
	openDefer bool
    // sp 記錄調(diào)用者棧指針,可以通過它判斷自己注冊的defer是否已經(jīng)執(zhí)行完了
	sp        uintptr  // sp at time of defer
    // pc deferproc的返回地址
	pc        uintptr  // pc at time of defer
    // fn 要注冊的funcval
	fn        *funcval // can be nil for open-coded defers
    // _panic 指向當(dāng)前的panic,表示這個(gè)defer是由這個(gè)panic觸發(fā)的
	_panic    *_panic  // panic that is running defer
    // link 鏈到前一個(gè)注冊的defer結(jié)構(gòu)體
	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.
    // 通過這些信息可以找到未注冊到鏈表的defer函數(shù)
	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將參數(shù)注冊的時(shí)候拷貝到堆上,執(zhí)行時(shí)再(將參數(shù)和返回值)拷貝回棧上

go會分配不同規(guī)格的_defer pool,執(zhí)行時(shí)從空閑_defer中取一個(gè)出來用,沒有合適的再進(jìn)行堆分配。用完以后再放回空閑_defer pool。以避免頻繁的堆分配和回收

優(yōu)化

go1.12中defer存在的問題:

  • defer信息主要存儲在堆上,要在堆和棧上來回拷貝返回值和參數(shù)很慢
  • defer結(jié)構(gòu)體通過鏈表鏈起來,而鏈表的操作也很慢

go1.13中defer的優(yōu)化:

  • 減少了defer信息的堆分配。再通過deferprocStack將整個(gè)defer注冊到defer鏈表中
  • 將一般情況的defer信息存儲在函數(shù)棧幀的局部變量區(qū)域
  • 顯示循環(huán)或者是隱式循環(huán)的defer還是需要用到go1.12中defer信息的堆分配
  • 官方給出的性能提升是30%

go1.14中defer的優(yōu)化:

  • 在編譯階段插入代碼,把defer函數(shù)的執(zhí)行邏輯展開在所屬函數(shù)內(nèi),避免創(chuàng)建defer結(jié)構(gòu)體,而且不需要注冊到defer鏈表。稱為 open coded defer
  • 與1.13一樣不適用于循環(huán)中的defer
  • 性能幾乎提升了一個(gè)數(shù)量級
  • open coded defer 中發(fā)生panic 或 調(diào)用runtime.Goexit(),后面未注冊到的defer函數(shù)無法執(zhí)行到,需要棧掃描。defer結(jié)構(gòu)體中就多添加了一些字段,借助這些字段可以找到未注冊到鏈表中的defer函數(shù)

結(jié)果就是defer變快了,但是panic變慢了

defer添加了局部變量去判斷是否需要執(zhí)行,需要執(zhí)行的話就將標(biāo)識df對應(yīng)的位上或一下,如果是有條件的defer,需要根據(jù)具體條件去或df

deferprocStack

// deferprocStack queues a new deferred function with a defer record on the stack.
// The defer record must have its siz and fn fields initialized.
// All other fields can contain junk.
// The defer record must be immediately followed in memory by
// the arguments of the defer.
// Nosplit because the arguments on the stack won't be scanned
// until the defer record is spliced into the gp._defer list.
//go:nosplit
func deferprocStack(d *_defer) {
    // 獲得當(dāng)前 goroutine
	gp := getg()
	if gp.m.curg != gp {
		// go code on the system stack can't defer
		throw("defer on system stack")
	}
	// siz and fn are already set.
	// The other fields are junk on entry to deferprocStack and
	// are initialized here.
    // 初始化 _defer 信息
	d.started = false
	d.heap = false
	d.openDefer = false
	d.sp = getcallersp()
	d.pc = getcallerpc()
	d.framepc = 0
	d.varp = 0
	// The lines below implement:
	//   d.panic = nil
	//   d.fd = nil
	//   d.link = gp._defer
	//   gp._defer = d
	// But without write barriers. The first three are writes to
	// the stack so they don't need a write barrier, and furthermore
	// are to uninitialized memory, so they must not use a write barrier.
	// The fourth write does not require a write barrier because we
	// explicitly mark all the defer structures, so we don't need to
	// keep track of pointers to them with a write barrier.
	*(*uintptr)(unsafe.Pointer(&d._panic)) = 0
	*(*uintptr)(unsafe.Pointer(&d.fd)) = 0
	*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
	*(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))

	return0()
	// No code can go here - the C return register has
	// been set and must not be clobbered.
}

到此這篇關(guān)于Golang的關(guān)鍵字defer的使用的文章就介紹到這了,更多相關(guān) Golang關(guān)鍵字defer內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go?GCM?gin中間件的加密解密文件流處理

    go?GCM?gin中間件的加密解密文件流處理

    這篇文章主要介紹了go語言?GCM加密解密,gin中間件的加密解密及文件流處理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>
    2022-05-05
  • go語言實(shí)現(xiàn)sftp包上傳文件和文件夾到遠(yuǎn)程服務(wù)器操作

    go語言實(shí)現(xiàn)sftp包上傳文件和文件夾到遠(yuǎn)程服務(wù)器操作

    這篇文章主要介紹了go語言實(shí)現(xiàn)sftp包上傳文件和文件夾到遠(yuǎn)程服務(wù)器操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Apache?IoTDB開發(fā)系統(tǒng)之Go原生接口方法

    Apache?IoTDB開發(fā)系統(tǒng)之Go原生接口方法

    這篇文章主要為大家介紹了?Apache?IoTDB開發(fā)系統(tǒng)之Go原生接口方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Go高級特性之并發(fā)處理http詳解

    Go高級特性之并發(fā)處理http詳解

    Golang?作為一種高效的編程語言,提供了多種方法來實(shí)現(xiàn)并發(fā)發(fā)送?HTTP?請求,本文將深入探討?Golang?中并發(fā)發(fā)送?HTTP?請求的最佳技術(shù)和實(shí)踐,希望對大家有所幫助
    2024-02-02
  • Go語言如何高效的進(jìn)行字符串拼接(6種方式對比分析)

    Go語言如何高效的進(jìn)行字符串拼接(6種方式對比分析)

    本文主要介紹了Go語言如何高效的進(jìn)行字符串拼接(6種方式對比分析),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • go語言結(jié)構(gòu)體指針操作示例詳解

    go語言結(jié)構(gòu)體指針操作示例詳解

    這篇文章主要為大家介紹了go語言結(jié)構(gòu)體指針操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • 基于Go語言實(shí)現(xiàn)插入排序算法及優(yōu)化

    基于Go語言實(shí)現(xiàn)插入排序算法及優(yōu)化

    插入排序是一種簡單的排序算法。這篇文章將利用Go語言實(shí)現(xiàn)冒泡排序算法,文中的示例代碼講解詳細(xì),對學(xué)習(xí)Go語言有一定的幫助,需要的可以參考一下
    2022-12-12
  • GO語言入門學(xué)習(xí)之基本數(shù)據(jù)類型字符串

    GO語言入門學(xué)習(xí)之基本數(shù)據(jù)類型字符串

    字符串在Go語言中以原生數(shù)據(jù)類型出現(xiàn),使用字符串就像使用其他原生數(shù)據(jù)類型(int、bool、float32、float64 等)一樣,下面這篇文章主要給大家介紹了關(guān)于GO語言入門學(xué)習(xí)之基本數(shù)據(jù)類型字符串的相關(guān)資料,需要的朋友可以參考下
    2022-04-04
  • Go語言基礎(chǔ)學(xué)習(xí)之指針詳解

    Go語言基礎(chǔ)學(xué)習(xí)之指針詳解

    Go 語言中指針是很容易學(xué)習(xí)的,Go 語言中使用指針可以更簡單的執(zhí)行一些任務(wù)。所以本文就來和大家聊聊Go語言中指針的定義與使用,需要的可以參考一下
    2022-12-12
  • GO語言中常見的排序算法使用示例

    GO語言中常見的排序算法使用示例

    這篇文章主要為大家介紹了GO語言中常見排序算法的使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04

最新評論