各位读者朋友,很高兴大家通过本博客学习 Go 语言,感谢一路相伴!《Go语言设计与实现》的纸质版图书已经上架京东,有需要的朋友请点击 链接 购买。
deferdeferdeferdeferdeferdeferdeferdeferfunc createPost(db *gorm.DB) error {
tx := db.Begin()
defer tx.Rollback()
if err := tx.Create(&Post{Author: "Draveness"}).Error; err != nil {
return err
}
return tx.Commit().Error
}
Rollbacktx.Commit()tx.Rollback()5.3.1 现象 #
deferdeferdeferdefer作用域 #
deferfordeferfunc main() {
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
}
$ go run main.go
4
3
2
1
0
deferdeferfmt.Println(4)deferfunc main() {
{
defer fmt.Println("defer runs")
fmt.Println("block ends")
}
fmt.Println("main ends")
}
$ go run main.go
block ends
main ends
defer runs
defer预计算参数 #
defermainfunc main() {
startedAt := time.Now()
defer fmt.Println(time.Since(startedAt))
time.Sleep(time.Second)
}
$ go run main.go
0s
defertime.Since(startedAt)maindeferdeferfunc main() {
startedAt := time.Now()
defer func() { fmt.Println(time.Since(startedAt)) }()
time.Sleep(time.Second)
}
$ go run main.go
1s
defertime.Since(startedAt)main5.3.2 数据结构 #
deferdefertype _defer struct {
siz int32
started bool
openDefer bool
sp uintptr
pc uintptr
fn *funcval
_panic *_panic
link *_defer
}
runtime._deferlink图 5-10 延迟调用链表
runtime._defersizsppcfndefer_panicopenDeferdeferruntime._defer5.3.3 执行机制 #
cmd/compile/internal/gc.state.stmtdeferfunc (s *state) stmt(n *Node) {
...
switch n.Op {
case ODEFER:
if s.hasOpenDefers {
s.openDeferRecord(n.Left) // 开放编码
} else {
d := callDefer // 堆分配
if n.Esc == EscNever {
d = callDeferStack // 栈分配
}
s.callResult(n.Left, d)
}
}
}
deferruntime._deferdeferdefer5.3.4 堆上分配 #
cmd/compile/internal/gc.state.stmtdeferruntime._defercmd/compile/internal/gc.state.callResultcmd/compile/internal/gc.state.calldefercmd/compile/internal/gc.state.callcmd/compile/internal/gc.state.newValue1Adeferfunc (s *state) call(n *Node, k callKind, returnResultAddr bool) *ssa.Value {
...
var call *ssa.Value
if k == callDeferStack {
// 在栈上初始化 defer 结构体
...
} else {
...
switch {
case k == callDefer:
aux := ssa.StaticAuxCall(deferproc, ACArgs, ACResults)
call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, aux, s.mem())
...
}
call.AuxInt = stksize
}
s.vars[&memVar] = call
...
}
deferruntime.deferprocdeferruntime.deferprocdeferruntime.deferreturnfunc (s *state) exit() *ssa.Block {
if s.hasdefer {
...
s.rtcall(Deferreturn, true, nil)
}
...
}
runtime._deferdeferruntime.deferprocdeferruntime.deferreturndeferdefer创建延迟调用 #
runtime.deferprocdeferruntime._deferfnpcspfunc deferproc(siz int32, fn *funcval) {
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.fn = fn
d.pc = callerpc
d.sp = sp
switch siz {
case 0:
case sys.PtrSize:
*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
default:
memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
}
return0()
}
runtime.return0runtime.deferreturnruntime.deferprocruntime.newdeferruntime._defersched.deferpoolpp.deferpoolruntime.mallocgcfunc 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 {
for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
d := sched.deferpool[sc]
sched.deferpool[sc] = d.link
pp.deferpool[sc] = append(pp.deferpool[sc], d)
}
}
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 {
total := roundupsize(totaldefersize(uintptr(siz)))
d = (*_defer)(mallocgc(total, deferType, true))
}
d.siz = siz
d.link = gp._defer
gp._defer = d
return d
}
runtime._defer_defer图 5-11 追加新的延迟调用
deferdeferdefer执行延迟调用 #
runtime.deferreturn_deferruntime._deferruntime.jmpdeferfunc deferreturn(arg0 uintptr) {
gp := getg()
d := gp._defer
if d == nil {
return
}
sp := getcallersp()
...
switch d.siz {
case 0:
case sys.PtrSize:
*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
default:
memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
}
fn := d.fn
gp._defer = d.link
freedefer(d)
jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}
runtime.jmpdeferdeferruntime.deferreturnTEXT runtime·jmpdefer(SB), NOSPLIT, $0-8
MOVL fv+0(FP), DX // fn
MOVL argp+4(FP), BX // caller sp
LEAL -4(BX), SP // caller sp after CALL
#ifdef GOBUILDMODE_shared
SUBL $16, (SP) // return to CALL again
#else
SUBL $5, (SP) // return to CALL again
#endif
MOVL 0(DX), BX
JMP BX // but first run the deferred function
runtime.deferreturn_defer5.3.5 栈上分配 #
runtime._deferdefercmd/compile/internal/gc.state.callruntime.deferprocStackfunc (s *state) call(n *Node, k callKind) *ssa.Value {
...
var call *ssa.Value
if k == callDeferStack {
// 在栈上创建 _defer 结构体
t := deferstruct(stksize)
...
ACArgs = append(ACArgs, ssa.Param{Type: types.Types[TUINTPTR], Offset: int32(Ctxt.FixedFrameSize())})
aux := ssa.StaticAuxCall(deferprocStack, ACArgs, ACResults) // 调用 deferprocStack
arg0 := s.constOffPtrSP(types.Types[TUINTPTR], Ctxt.FixedFrameSize())
s.store(types.Types[TUINTPTR], arg0, addr)
call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, aux, s.mem())
call.AuxInt = stksize
} else {
...
}
s.vars[&memVar] = call
...
}
runtime._deferruntime.deferprocStackruntime._deferfunc deferprocStack(d *_defer) {
gp := getg()
d.started = false
d.heap = false // 栈上分配的 _defer
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
*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
*(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))
return0()
}
runtime._deferruntime._deferdefer5.3.5 开放编码 #
deferdeferfuncdatapanicdeferWith normal (stack-allocated) defers only: 35.4 ns/op
With open-coded defers: 5.6 ns/op
Cost of function call alone (remove defer keyword): 4.4 ns/op
deferdeferdeferreturndefer初看上述几个条件可能会觉得不明所以,但是当我们深入理解基于开放编码的优化就可以明白上述限制背后的原因,除了上述几个条件之外,也有其他的条件会限制开放编码的使用,不过这些都是不太重要的细节,我们在这里也不会深究。
启用优化 #
cmd/compile/internal/gc.walkstmtOpenCodedDeferDisallowedconst maxOpenDefers = 8
func walkstmt(n *Node) *Node {
switch n.Op {
case ODEFER:
Curfn.Func.SetHasDefer(true)
Curfn.Func.numDefers++
if Curfn.Func.numDefers > maxOpenDefers {
Curfn.Func.SetOpenCodedDeferDisallowed(true)
}
if n.Esc != EscNever {
Curfn.Func.SetOpenCodedDeferDisallowed(true)
}
fallthrough
...
}
}
deferdeferfordefercmd/compile/internal/gc.buildssadeferfunc buildssa(fn *Node, worker int) *ssa.Func {
...
s.hasOpenDefers = s.hasdefer && !s.curfn.Func.OpenCodedDeferDisallowed()
...
if s.hasOpenDefers &&
s.curfn.Func.numReturns*s.curfn.Func.numDefers > 15 {
s.hasOpenDefers = false
}
...
}
defer延迟记录 #
defercmd/compile/internal/gc.buildssadeferBitsfunc buildssa(fn *Node, worker int) *ssa.Func {
...
if s.hasOpenDefers {
deferBitsTemp := tempAt(src.NoXPos, s.curfn, types.Types[TUINT8]) // 初始化延迟比特
s.deferBitsTemp = deferBitsTemp
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)
s.vars[&memVar] = s.newValue1Apos(ssa.OpVarLive, types.TypeMem, deferBitsTemp, s.mem(), false)
}
}
defer图 5-12 延迟比特
deferifdeferdeferBits := 0 // 初始化 deferBits
_f1, _a1 := f1, a1 // 保存函数以及参数
deferBits |= 1 << 0 // 将 deferBits 最后一位置位 1
if condition {
_f2, _a2 := f2, a2 // 保存函数以及参数
deferBits |= 1 << 1 // 将 deferBits 倒数第二位置位 1
}
exit:
if deferBits & 1 << 1 != 0 {
deferBits &^= 1 << 1
_f2(a2)
}
if deferBits & 1 << 0 != 0 {
deferBits &^= 1 << 0
_f1(a1)
}
deferdeferBitsdeferBitsdeferdefercmd/compile/internal/gc.openDeferInfotype openDeferInfo struct {
n *Node
closure *ssa.Value
closureNode *Node
rcvr *ssa.Value
rcvrNode *Node
argVals []*ssa.Value
argNodes []*Node
}
cmd/compile/internal/gc.buildssacmd/compile/internal/gc.state.openDeferRecordclosurercvrargValsdeferdefercmd/compile/internal/gc.state.openDeferExitdeferBitsruntime.deferreturndeferfunc deferreturn(arg0 uintptr) {
gp := getg()
d := gp._defer
sp := getcallersp()
if d.openDefer {
runOpenDeferFrame(gp, d)
gp._defer = d.link
freedefer(d)
return
}
...
}
runtime.runOpenDeferFrameruntime._deferdeferBitsdeferdeferBitsruntime.reflectcallSavedeferfunc runOpenDeferFrame(gp *g, d *_defer) bool {
fd := d.fd
...
deferBitsOffset, fd := readvarintUnsafe(fd)
nDefers, fd := readvarintUnsafe(fd)
deferBits := *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset)))
for i := int(nDefers) - 1; i >= 0; i-- {
var argWidth, closureOffset, nArgs uint32 // 读取函数的地址和参数信息
argWidth, fd = readvarintUnsafe(fd)
closureOffset, fd = readvarintUnsafe(fd)
nArgs, fd = readvarintUnsafe(fd)
if deferBits&(1<<i) == 0 {
...
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
memclrNoHeapPointers(deferArgs, uintptr(argWidth))
...
}
return done
}
defer5.3.7 小结 #
defer我们在本节前面提到的两个现象在这里也可以解释清楚了:
deferdefer_deferruntime._defer5.3.8 延伸阅读 #
上一节 下一节