本从以go-1.16版本源码为基础,介绍了defer关键字的使用规则、实现原理和优化路线,最后介绍了几种将近的使用场景。试图对 go defer 关键字应用到实现原理有一个全面的了解。

欢迎关注公众号闲余说

defer 概述
deferdefer

简单理解一下:

deferreturnpanic

另外,在《effective go》中也有相关描述:

deferdefer
defer  defer
defer
deferC++defer
defer使用规则
godefer

但是理解起来,可能并没有那么容易,归纳其他主要有如下特点:

deferdefer延迟函数defer延迟函数延迟函数return延迟函数return延迟函数nil延迟函数deferpanicdefer
defer

执行顺序

deferLIFO
defermaindeferdeferAdeferBdeferCdeferCdeferBdeferAC++

示例代码:

// 演示defer执行顺序
package main

import "fmt"

func deferA() {
	fmt.Println("deferA")
}

func deferB() {
	fmt.Println("deferB")
}

func deferC() {
	fmt.Println("deferC")
}

func main() {
	defer deferA()
	defer deferB()
	defer deferC()
	fmt.Println("main")
}

上述示例代码执行结果:

$ go run defer_1.go 
main
deferC
deferB
deferA

defer 与 return 顺序

示例代码:

// 验证 defer 与 return 执行顺序
package main

import "fmt"

func deferFunc() int {
	fmt.Println("defer func is called")
	return 0
}

func returnFunc() int {
	fmt.Println("return func is called")
	return 0
}

func returnAndDefer() int {
	defer deferFunc()
	return returnFunc()
}

func main() {
	returnAndDefer()
}

上述示例代码执行结果:

$ go run defer_4.go 
return func is called
defer func is called
defer延迟函数deferFunc()return returnFunc()
图1 defer 与 return 执行顺序

预计算

adeferdefer

示例代码:

// 演示defer预计算
package main

import "fmt"

func deferD(d int) {
	fmt.Println(d)
}

func main() {
	a := 1
	defer deferD(a)
	a = a + 1
}

上述代示例码执行结果:

$ go run defer_2.go 
1
延迟函数
// 验证defer预计算示例2
package main

import "fmt"

func f(index int, value int) int {
	fmt.Printf("index=%d,value=%d\n", index, value)
	return index
}

func main() {
	defer f(1, f(3, 1))
	defer f(2, f(4, 2))
}

上述示例代码执行结果:

$ go run defer_2_2.go 
index=3,value=1
index=4,value=2
index=2,value=4
index=1,value=3
defer
deferf(1,f(3,1))f(3,1)index=3,value=1deferf(2,f(4,2))f(4,2)index=4,value=2deferf(2,f(4,2))index=2,value=4deferf(1,f(3,1))index=1,value=3

修改命名返回值

示例代码:

// 演示 defer修改有命名返回值函数的返回值
package main

import "fmt"

func func1(a int) (e int) {
	defer func() {
		e = a + 1
	}()
	return a
}

func main() {
	e := func1(1)
	fmt.Println(e)
}

上述示例代码执行结果:

$ go run defer_3.go 
2
func1eadefer2.1defer延迟函数amainereturn amaina+1
go
  1. 命名返回值,如果指定了一个返回值的名字,则会在函数起始处被初始化为对应类型的零值并且作用域为整个函数。可以视为在该函数的第一行中定义了该名字的变量。
  2. 匿名返回值,如果没有指定返回值的名字,则是在返回时创建一个临时变量来接收返回值。

示例代码:

package main

import "fmt"

func funcE() (t int) {
	fmt.Printf("t = %v\n", t)
	return 2
}

func main() {
	funcE()
}

上述示例代码执行结果:

$ go run defer_5.go 
t = 0

defer 与 panic

panicdeferdeferrecoverpanic延迟函数panic

defer 函数不捕获异常

示例代码:

// 演示defer不捕获异常
package main

import (
	"fmt"
)

func deferCall() {
	defer func() { fmt.Println("defer 1: before panic print") }()
	defer func() { fmt.Println("defer 2: before panic print") }()
	panic("panic error") // trigger defer out stack
	defer func() { fmt.Println("defer 3: after panic, never exec") }()
}

func main() {
	deferCall()
	fmt.Println("main exec ok")
}

上述示例代码执行结果:

$ go run defer_panic_1.go 
defer 2: before panic print
defer 1: before panic print
panic: panic error

goroutine 1 [running]:
main.deferCall()
        /home/work/workspace/defer/defer_panic_1.go:11 +0x68
main.main()
        /home/work/workspace/defer/defer_panic_1.go:16 +0x25
exit status 2
panicdeferpanic

defer 函数捕获异常

示例代码:

// 演示defer捕获异常
package main

import (
	"fmt"
)

func deferCall() {
	defer func() { fmt.Println("defer 1: before panic print") }()
	defer func() {
		fmt.Println("defer 2: before panic print,recover")
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	defer func() { fmt.Println("defer 3: before panic print") }()
	panic("panic error") // trigger defer out stack
	defer func() { fmt.Println("defer 4: after panic, never exec") }()
}
func main() {
	deferCall()
	fmt.Println("main exec ok")
}

上述示例代码执行结果:

$ go run defer_panic_2.go 
defer 3: before panic print
defer 2: before panic print,recover
panic error
defer 1: before panic print
main exec ok
panicdeferdefer延迟函数deferCallpanicdeferdeferCallmain
延迟函数recover()panic()

defer 函数中包含panic

示例代码:

// 演示defer抛出异常
package main

import (
	"fmt"
)

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("defer1 recover:", err)
		} else {
			fmt.Println("fatal")
		}
	}()
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
		panic("defer panic")
	}()
	panic("main panic")
}

上述示例代码执行结果:

$ go run defer_panic_3.go 
main panic
defer1 recover: defer panic
mainpanicmain panicdeferdeferpanicdefer
图2 panic 可以沿着defer执行路径上抛或者被recover
defer 实现
deferdefergogo 1.6

defer执行机制

数据结构

defer
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
sizstartedheapopenDeferdefersppcfndefer_panic_panic paniclink
图3 defer 结构

执行机制

gc.state.stmtdefer
// stmt converts the statement n to SSA and adds it to s.
func (s *state) stmt(n *Node) {
  // ...
	case ODEFER:
	// ...
		if s.hasOpenDefers {
			s.openDeferRecord(n.Left) // 开放编码
		} else {
			d := callDefer  // 默认是堆上实现
			if n.Esc == EscNever {
				d = callDeferStack // 栈上实现
			}
			s.callResult(n.Left, d)
		}
  // ...
}
ssagen.state.stmt
deferruntime._deferdefer
gc.state.stmt
s.hasOpenDefers==trueopenDeferRecord n.Esc == EscNevercallKindcallDeferStackcallResultcallKindcallDefercallResult
defer

堆上实现

defer
gc.state.callResultgc.state.calldefer
gc.state.call
gc.state.newValue1Adefer
// Calls the function n using the specified call type.
// Returns the address of the return value (or nil if none).
func (s *state) call(n *Node, k callKind, returnResultAddr bool) *ssa.Value {
	// ...
	if k == callDeferStack { // 栈上实现逻辑分支,后面会介绍
		// ....
	} else {
		// ...
		// call target
		switch {
		case k == callDefer:
			aux := ssa.StaticAuxCall(deferproc, ACArgs, ACResults) // deferproc defer创建函数
			if testLateExpansion {
				call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
				call.AddArgs(callArgs...)
			} else {
				call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, aux, s.mem())
			}
		// ....
		call.AuxInt = stksize // Call operations carry the argsize of the callee along with them
	}
	// ...
}
gc.state.call

核心思想:

deferruntime.deferreturn
// exit processes any code that needs to be generated just before returning.
// It returns a BlockRet block that ends the control flow. Its control value
// will be set to the final memory state.
func (s *state) exit() *ssa.Block {
	if s.hasdefer {
		if s.hasOpenDefers { // 开放编码实现处理逻辑,后续会介绍
		} else {
			s.rtcall(Deferreturn, true, nil)
		}
	}
}
defer
runtime.deferprocruntime.deferreturndefer

创建延迟调用

// 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
  // 获取当前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.
   // 获取调用者指针
	sp := getcallersp()
  // 通过偏移获得参数
	argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
	callerpc := getcallerpc()
	d := newdefer(siz) // 创建了一个新的_defer 对象
	if d._panic != nil {
		throw("deferproc: d.panic != nil after newdefer")
	}
  // 注意这里,可以看出,_defer链表是头插的,这是为什么defer是逆序执行的原因
	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.
}
runtime.newdeferruntime._defer
sched.deferpool_defergoroutinegoroutinepp.deferpool_deferpp.deferpool_deferruntime.mallocgc_defer
// 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)) {// 从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)
        // 先去sched.deferpool回收一批_defer对象,转移到pp.deferpool中
				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)
			})
		}
    // 尝试从pp.deferpool中取个空闲的_defer对象
		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
}
runtime._deferruntime.g_deferdefer

执行延迟调用

runtime.deferreturn_deferruntime._deferruntime.jmpdefer
// Run a deferred function if there is one.
// The compiler inserts a call to this at the end of any
// function which calls defer.
// If there is a deferred function, this will call runtime·jmpdefer,
// which will jump to the deferred function such that it appears
// to have been called by the caller of deferreturn at the point
// just before deferreturn was called. The effect is that deferreturn
// is called again and again until there are no more deferred functions.
//
// Declared as nosplit, because the function should not be preempted once we start
// modifying the caller's frame in order to reuse the frame to call the deferred
// function.
//
// The single argument isn't actually used - it just has its address
// taken so it can be matched against pending defers.
//go:nosplit
func deferreturn(arg0 uintptr) {
	gp := getg()
	d := gp._defer // 取出第一个_defer对象
	if d == nil {
		return
	}
	// ...
	// 开放编码实现处理逻辑
	if d.openDefer {
	}

	fn := d.fn
	d.fn = nil
	gp._defer = d.link // 从g._defer链表中删除当前_defer对象
	freedefer(d) // 释放_defer对象
	// If the defer function pointer is nil, force the seg fault to happen
	// here rather than in jmpdefer. gentraceback() throws an error if it is
	// called with a callback on an LR architecture and jmpdefer is on the
	// stack, because the stack trace can be incorrect in that case - see
	// issue #8153).
	_ = fn.fn
	jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) // 调用jmpdefer
}
runtime.jmpdefer
defer runtime.deferreturn
runtime.deferreturngoroutine_defer_defer

栈上实现

defer
deferdefer
defer
deferruntime.deferprocStackdeferdeferdeferdeferdefer
func (s *state) call(n *Node, k callKind, returnResultAddr bool) *ssa.Value {
	if k == callDeferStack {
		testLateExpansion = ssa.LateCallExpansionEnabledWithin(s.f)
		// Make a defer struct d on the stack.在栈上创建_defer结构
		t := deferstruct(stksize)
		d := tempAt(n.Pos, s.curfn, t)

		s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, d, s.mem())
		addr := s.addr(d)

		// Must match reflect.go:deferstruct and src/runtime/runtime2.go:_defer.
		// 0: siz
		s.store(types.Types[TUINT32],
			s.newValue1I(ssa.OpOffPtr, types.Types[TUINT32].PtrTo(), t.FieldOff(0), addr),
			s.constInt32(types.Types[TUINT32], int32(stksize)))
		// 1: started, set in deferprocStack
		// 2: heap, set in deferprocStack
		// 3: openDefer
		// 4: sp, set in deferprocStack
		// 5: pc, set in deferprocStack
		// 6: fn
		s.store(closure.Type,
			s.newValue1I(ssa.OpOffPtr, closure.Type.PtrTo(), t.FieldOff(6), addr),
			closure)
		// 7: panic, set in deferprocStack
		// 8: link, set in deferprocStack
		// 9: framepc
		// 10: varp
		// 11: fd

		// Then, store all the arguments of the defer call.
		ft := fn.Type
		off := t.FieldOff(12)
		args := n.Rlist.Slice()

		// Set receiver (for interface calls). Always a pointer.
		if rcvr != nil {
			p := s.newValue1I(ssa.OpOffPtr, ft.Recv().Type.PtrTo(), off, addr)
			s.store(types.Types[TUINTPTR], p, rcvr)
		}
		// Set receiver (for method calls).
		if n.Op == OCALLMETH {
			f := ft.Recv()
			s.storeArgWithBase(args[0], f.Type, addr, off+f.Offset)
			args = args[1:]
		}
		// Set other args.
		for _, f := range ft.Params().Fields().Slice() {
			s.storeArgWithBase(args[0], f.Type, addr, off+f.Offset)
			args = args[1:]
		}

		// Call runtime.deferprocStack with pointer to _defer record.
		ACArgs = append(ACArgs, ssa.Param{Type: types.Types[TUINTPTR], Offset: int32(Ctxt.FixedFrameSize())})
		aux := ssa.StaticAuxCall(deferprocStack, ACArgs, ACResults) // 调用deferprocStack
	// ...
	} else {
		// 堆上实现
	}
	// 栈上和堆上实现的共同逻辑
}

runtime._deferruntime.deferprocStackruntime._defer
// 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) { // 注意这里入参已经是_defer了,因此deferprocStack只是做一些简单的初始化,然后将初始化好的_defer对象插入当前goroutine的_defer链表中
	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.
	d.started = false
	d.heap = false
	d.openDefer = false
	d.sp = getcallersp()
	d.pc = getcallerpc()
	d.framepc = 0
	d.varp = 0

	*(*uintptr)(unsafe.Pointer(&d._panic)) = 0
	*(*uintptr)(unsafe.Pointer(&d.fd)) = 0
  // 将初始化好的_defer对象插入当前goroutine的_defer链表中
	*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
	*(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d)) 

	return0()
}
runtime._defer

开放编码实现

defer
defer_defer_defer open coded deferdeferopen coded deferpanicruntime.Goexit()deferdeferdefer

开启开放编码

defer
deferdeferreturndefer
deferdeferfor
// The max number of defers in a function using open-coded defers. We enforce this
// limit because the deferBits bitmask is currently a single byte (to minimize code size)
const maxOpenDefers = 8
// The result of walkstmt MUST be assigned back to n, e.g.
// 	n.Left = walkstmt(n.Left)
func walkstmt(n *Node) *Node {
	// ...
	switch n.Op {
	// ...

	case ODEFER:
		Curfn.Func.SetHasDefer(true)
		Curfn.Func.numDefers++
		if Curfn.Func.numDefers > maxOpenDefers { // maxOpenDefers == 8 defer 个数大于8个,不行
			// Don't allow open-coded defers if there are more than
			// 8 defers in the function, since we use a single
			// byte to record active defers.
			Curfn.Func.SetOpenCodedDeferDisallowed(true)
		}
		if n.Esc != EscNever { // defer在循环中也不行
			// If n.Esc is not EscNever, then this defer occurs in a loop,
			// so open-coded defers cannot be used in this function.
			Curfn.Func.SetOpenCodedDeferDisallowed(true)
		}
		fallthrough
		// ...
	return n
}
gc.buildssadefer
/ buildssa builds an SSA function for fn.
// worker indicates which of the backend workers is doing the processing.
func buildssa(fn *Node, worker int) *ssa.Func {
	// ...
	s.hasOpenDefers = Debug.N == 0 && s.hasdefer && !s.curfn.Func.OpenCodedDeferDisallowed()
	switch {
	case s.hasOpenDefers && (Ctxt.Flag_shared || Ctxt.Flag_dynlink) && thearch.LinkArch.Name == "386":
		// Don't support open-coded defers for 386 ONLY when using shared
		// libraries, because there is extra code (added by rewriteToUseGot())
		// preceding the deferreturn/ret code that is generated by gencallret()
		// that we don't track correctly.
		s.hasOpenDefers = false
	}
	if s.hasOpenDefers && s.curfn.Func.Exit.Len() > 0 {
		// Skip doing open defers if there is any extra exit code (likely
		// copying heap-allocated return values or race detection), since
		// we will not generate that code in the case of the extra
		// deferreturn/ret segment.
		s.hasOpenDefers = false
	}
	if s.hasOpenDefers &&
		s.curfn.Func.numReturns*s.curfn.Func.numDefers > 15 { // 返回语句的数量与defer数量的乘积需要小于 15
		// Since we are generating defer calls at every exit for
		// open-coded defers, skip doing open-coded defers if there are
		// too many returns (especially if there are multiple defers).
		// Open-coded defers are most important for improving performance
		// for smaller functions (which don't have many returns).
		s.hasOpenDefers = false
	}
	// ... 
}

设置

s.hasOpenDefers == true
deferBitsdeferBitsbitmaskdefer_defer
func buildssa(fn *Node, worker int) *ssa.Func {
	// ...
	if s.hasOpenDefers {
		// Create the deferBits variable and stack slot.  deferBits is a
		// bitmask showing which of the open-coded defers in this function
		// have been activated.
		deferBitsTemp := tempAt(src.NoXPos, s.curfn, types.Types[TUINT8])
		s.deferBitsTemp = deferBitsTemp
		// For this value, AuxInt is initialized to zero by default
		startDeferBits := s.entryNewValue0(ssa.OpConst8, types.Types[TUINT8])
		s.vars[&deferBitsVar] = startDeferBits
		s.deferBitsAddr = s.addr(deferBitsTemp)
		s.store(types.Types[TUINT8], s.deferBitsAddr, startDeferBits)
		// Make sure that the deferBits stack slot is kept alive (for use
		// by panics) and stores to deferBits are not eliminated, even if
		// all checking code on deferBits in the function exit can be
		// eliminated, because the defer statements were all
		// unconditional.
		s.vars[&memVar] = s.newValue1Apos(ssa.OpVarLive, types.TypeMem, deferBitsTemp, s.mem(), false)
	}
	// ...
}
deferBitsdefer
图4 Golang deferBits 示意
gc.state.stmtgc.state.openDeferRecordgc.openDeferInfoclosurercvrargVals
// Information about each open-coded defer.
type openDeferInfo struct {
	// The ODEFER node representing the function call of the defer
	n *Node
	// If defer call is closure call, the address of the argtmp where the
	// closure is stored.
	closure *ssa.Value
	// The node representing the argtmp where the closure is stored - used for
	// function, method, or interface call, to store a closure that panic
	// processing can use for this defer.
	closureNode *Node
	// If defer call is interface call, the address of the argtmp where the
	// receiver is stored
	rcvr *ssa.Value
	// The node representing the argtmp where the receiver is stored
	rcvrNode *Node
	// The addresses of the argtmps where the evaluated arguments of the defer
	// function call are stored.
	argVals []*ssa.Value
	// The nodes representing the argtmps where the args of the defer are stored
	argNodes []*Node
}
// openDeferRecord adds code to evaluate and store the args for an open-code defer
// call, and records info about the defer, so we can generate proper code on the
// exit paths. n is the sub-node of the defer node that is the actual function
// call. We will also record funcdata information on where the args are stored
// (as well as the deferBits variable), and this will enable us to run the proper
// defer calls during panics.
func (s *state) openDeferRecord(n *Node) {
	// ...
	opendefer := &openDeferInfo{
		n: n,
	}
	fn := n.Left
	if n.Op == OCALLFUNC {
		// We must always store the function value in a stack slot for the
		// runtime panic code to use. But in the defer exit code, we will
		// call the function directly if it is a static function.
		closureVal := s.expr(fn)
		closure := s.openDeferSave(nil, fn.Type, closureVal)
		opendefer.closureNode = closure.Aux.(*Node)
		if !(fn.Op == ONAME && fn.Class() == PFUNC) {
			opendefer.closure = closure
		}
	} else if n.Op == OCALLMETH {
		if fn.Op != ODOTMETH {
			Fatalf("OCALLMETH: n.Left not an ODOTMETH: %v", fn)
		}
		closureVal := s.getMethodClosure(fn)
		// We must always store the function value in a stack slot for the
		// runtime panic code to use. But in the defer exit code, we will
		// call the method directly.
		closure := s.openDeferSave(nil, fn.Type, closureVal)
		opendefer.closureNode = closure.Aux.(*Node)
	} else {
		if fn.Op != ODOTINTER {
			Fatalf("OCALLINTER: n.Left not an ODOTINTER: %v", fn.Op)
		}
		closure, rcvr := s.getClosureAndRcvr(fn)
		opendefer.closure = s.openDeferSave(nil, closure.Type, closure)
		// Important to get the receiver type correct, so it is recognized
		// as a pointer for GC purposes.
		opendefer.rcvr = s.openDeferSave(nil, fn.Type.Recv().Type, rcvr)
		opendefer.closureNode = opendefer.closure.Aux.(*Node)
		opendefer.rcvrNode = opendefer.rcvr.Aux.(*Node)
	}
	for _, argn := range n.Rlist.Slice() {
		var v *ssa.Value
		if canSSAType(argn.Type) {
			v = s.openDeferSave(nil, argn.Type, s.expr(argn))
		} else {
			v = s.openDeferSave(argn, argn.Type, nil)
		}
		args = append(args, v)
		argNodes = append(argNodes, v.Aux.(*Node))
	}
	opendefer.argVals = args
	opendefer.argNodes = argNodes
	index := len(s.openDefers)
	s.openDefers = append(s.openDefers, opendefer)

	// Update deferBits only after evaluation and storage to stack of
	// args/receiver/interface is successful.
	bitvalue := s.constInt8(types.Types[TUINT8], 1<<uint(index))
	newDeferBits := s.newValue2(ssa.OpOr8, types.Types[TUINT8], s.variable(&deferBitsVar, types.Types[TUINT8]), bitvalue)
	s.vars[&deferBitsVar] = newDeferBits
	s.store(types.Types[TUINT8], s.deferBitsAddr, newDeferBits)
}

deferdefergc.state.exitgc.state.openDeferExitdeferBits
// exit processes any code that needs to be generated just before returning.
// It returns a BlockRet block that ends the control flow. Its control value
// will be set to the final memory state.
func (s *state) exit() *ssa.Block {
	if s.hasdefer {
		if s.hasOpenDefers {
			if shareDeferExits && s.lastDeferExit != nil && len(s.openDefers) == s.lastDeferCount {
				if s.curBlock.Kind != ssa.BlockPlain {
					panic("Block for an exit should be BlockPlain")
				}
				s.curBlock.AddEdgeTo(s.lastDeferExit)
				s.endBlock()
				return s.lastDeferFinalBlock
			}
			s.openDeferExit()
		} else {
			s.rtcall(Deferreturn, true, nil)
		}
	}
	// ...
}

执行

runtime.deferreturndefer
func deferreturn(arg0 uintptr) {
	gp := getg()
	d := gp._defer
	if d.openDefer {
		done := runOpenDeferFrame(gp, d)
		if !done {
			throw("unfinished open-coded defers in deferreturn")
		}
		gp._defer = d.link
		freedefer(d)
		return
	}
}
runtime.runOpenDeferFrame
runtime._deferdeferBitsdeferdeferBitsruntime.reflectcallSavedefer
// runOpenDeferFrame runs the active open-coded defers in the frame specified by
// d. It normally processes all active defers in the frame, but stops immediately
// if a defer does a successful recover. It returns true if there are no
// remaining defers to run in the frame.
func runOpenDeferFrame(gp *g, d *_defer) bool {
	done := true
	fd := d.fd

	// Skip the maxargsize
	_, fd = readvarintUnsafe(fd)
	deferBitsOffset, fd := readvarintUnsafe(fd)
	nDefers, fd := readvarintUnsafe(fd)
	deferBits := *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) // 拿到 deferBits

	for i := int(nDefers) - 1; i >= 0; i-- { // 遍历 deferBits
		// read the funcdata info for this defer
		if deferBits&(1<<i) == 0 {
			// 遍历,跳过不需要执行的defer
			continue
		}
		closure := *(**funcval)(unsafe.Pointer(d.varp - uintptr(closureOffset)))
		d.fn = closure
		// ...
		deferBits = deferBits &^ (1 << i)
		*(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) = deferBits
		p := d._panic
		reflectcallSave(p, unsafe.Pointer(closure), deferArgs, argWidth) // 处理需要被执行延迟函数
		if p != nil && p.aborted {
			break
		}
		d.fn = nil
		// These args are just a copy, so can be cleared immediately
		memclrNoHeapPointers(deferArgs, uintptr(argWidth))
		if d._panic != nil && d._panic.recovered {
			done = deferBits == 0
			break
		}
	}

	return done
}
runtime.reflectcallSaveruntime.reflectcall
// reflectcallSave calls reflectcall after saving the caller's pc and sp in the
// panic record. This allows the runtime to return to the Goexit defer processing
// loop, in the unusual case where the Goexit may be bypassed by a successful
// recover.
func reflectcallSave(p *_panic, fn, arg unsafe.Pointer, argsize uint32) {
	if p != nil { // 处理panic
		p.argp = unsafe.Pointer(getargp(0))
		p.pc = getcallerpc()
		p.sp = unsafe.Pointer(getcallersp())
	}
	reflectcall(nil, fn, arg, argsize, argsize)
	if p != nil {
		p.pc = 0
		p.sp = unsafe.Pointer(nil)
	}
}
open coded deferpanicruntime.Goexitdefer_deferdeferdeferpanic
defer
使用场景案例
defer

资源释放

C++RAIIRAII(Resource Acquisition Is Initialization)golangdefer
// defer 关闭文件
package main

import (
	"fmt"
	"os"
)

func main() {
	fileHandler, err := os.Open("./test.txt")
	if nil != err {
		panic(err)
	}
	//检查完,发现没有错误,就可以关闭使用defer来关闭
	defer func() {
		err := fileHandler.Close()
		if nil != err {
			fmt.Println("defer关闭文件失败:", err)
		} else {
			fmt.Println("defer 关闭文件成功")
		}
	}()
}

defer 关闭文件成功

上报

defer
// defer 日志处理
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func testDefer() {
	a, b := 0, 1
	defer func(a, b *int) {
		fmt.Printf("a=%d,b=%d\n", *a, *b)
	}(&a, &b)

	rand.Seed(time.Now().Unix())
	if rand.Int()%2 == 0 {
		a, b = 1, 2
	} else {
		return
	}
}
func main() {
	testDefer()
}
a=1,b=2
a=0,b=1
defer

函数执行时间

有这样的一个场景,我们需要获取一个函数的耗时,在go语言中我们会怎么做呢?

在其他语言中,可能是这样操作:

startendend-start
defer
func slowOperation() {
	defer trace("slowOperation")() // 注意函数调用,不能漏掉最后的圆括号
	time.Sleep(10 * time.Second)
}

func trace(msg string) func() {
	start := time.Now()
	return func() { log.Printf("exit %s time_cost=%s", msg, time.Since(start)) }
}
2021/12/04 23:06:42 exit slowOperation time_cost=10.000143172s
小结
deferdefer
图5 Golang defer 优化路线

三种实现机制,并不是替代关系,而是,特殊化处理的关系,条件越来越苛刻。虽然性能不断提升,但机制适用范围越来越窄。

参考文献
  • [1] https://go.dev/ref/spec#Defer_statements
  • [2] https://go.dev/doc/effective_go#defer
  • [3] https://segmentfault.com/a/1190000022112411
  • [5] https://zhuanlan.zhihu.com/p/56557423
  • [6]https://juejin.cn/post/7101887123539623944
  • [7]引入栈 https://go-review.googlesource.com/c/go/+/171758
  • [8]引入开放编码 https://go-review.googlesource.com/c/go/+/190098/6
  • [9]优化效果 https://github.com/golang/proposal/blob/master/design/34481-opencoded-defers.md
  • [10] https://www.topgoer.com/%E5%87%BD%E6%95%B0/%E5%BB%B6%E8%BF%9F%E8%B0%83%E7%94%A8defer.html
  • [11] https://www.luozhiyun.com/archives/523