defer 底层实现总结
...大约 4 分钟
1. 数据结构
runtime._defer
是延迟调用链表上的元素,所有的runtime._defer
构成一个单向链表。
type _defer struct {
...
started bool
openDefer bool
sp uintptr // sp at time of defer
pc uintptr // pc at time of defer
fn func() // can be nil for open-coded defers
_panic *_panic // panic that is running defer
link *_defer // next defer on G; can point to either heap or stack!
...
}
sp
:栈指针pc
:程序计数器fn
:defer
语句中传入的函数_panic
:触发延迟调用的 panicopenDefer
:是否经过开放编码优化
2. 内存分配
Golang 会根据 defer
语句的函数决定不同的内存分配方式:
- 堆上分配
- 栈上分配
- 开放编码
3. 创建 defer
// Create a new deferred function fn, which has no arguments and results.
// The compiler turns a defer statement into a call to this.
func deferproc(fn func()) {
gp := getg()
if gp.m.curg != gp {
// go code on the system stack can't defer
throw("defer on system stack")
}
d := newdefer()
if d._panic != nil {
throw("deferproc: d.panic != nil after newdefer")
}
d.link = gp._defer
gp._defer = d
d.fn = fn
d.pc = getcallerpc()
// We must not be preempted between calling getcallersp and
// storing it to d.sp because getcallersp's result is a
// uintptr stack pointer.
d.sp = getcallersp()
// 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.
}
newdefer()
:获取defer
结构体- 设置相关字段
defer
结构体
获取 runtime.newdefer
有三种方式获取runtime._defer
结构体:
- 调度器的延迟调用缓存池
sched.deferpool
中取出结构体并将该结构体追加到当前 Goroutine 的缓存池中 - 从 Goroutine 的延迟调用缓存池
pp.deferpool
中取出结构体 - 通过
runtime.mallocgc
在堆上创建一个新的结构体
获取到的runtime._defeer
会被追加到defer
链表的 head 位置。
// 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.
func newdefer() *_defer {
var d *_defer
mp := acquirem()
pp := mp.p.ptr()
if len(pp.deferpool) == 0 && sched.deferpool != nil {
lock(&sched.deferlock)
for len(pp.deferpool) < cap(pp.deferpool)/2 && sched.deferpool != nil {
d := sched.deferpool
sched.deferpool = d.link
d.link = nil
pp.deferpool = append(pp.deferpool, d)
}
unlock(&sched.deferlock)
}
if n := len(pp.deferpool); n > 0 {
d = pp.deferpool[n-1]
pp.deferpool[n-1] = nil
pp.deferpool = pp.deferpool[:n-1]
}
releasem(mp)
mp, pp = nil, nil
if d == nil {
// Allocate new defer.
d = new(_defer)
}
d.heap = true
return d
}
执行顺序
对于defer
链表,新元素插入到链表头部,而执行则是从头到尾方向,所以defer
语句是按照 LIFO 的顺序执行。
拷贝参数
调用 runtime.deferproc
函数创建新的延迟调用时就会立刻拷贝函数的参数,函数的参数不会等到真正执行时计算;
defer
4. 执行 runtime.deferreturn
会从 Goroutine 的 _defer
链表中取出最前面的 runtime._defer
并调用 runtime.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
if d == nil {
return
}
sp := getcallersp()
if d.sp != sp {
return
}
if d.openDefer {
done := runOpenDeferFrame(gp, d)
if !done {
throw("unfinished open-coded defers in deferreturn")
}
gp._defer = d.link
freedefer(d)
return
}
// Moving arguments around.
//
// Everything called after this point must be recursively
// nosplit because the garbage collector won't know the form
// of the arguments until the jmpdefer can flip the PC over to
// fn.
switch d.siz {
case 0:
// Do nothing.
case sys.PtrSize:
*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
default:
memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
}
fn := d.fn
d.fn = nil
gp._defer = d.link
freedefer(d)
// 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)))
}
runtime.jmpdefer
是一个用汇编语言实现的运行时函数,它的主要工作是跳转到 defer
所在的代码段并在执行结束之后跳转回 runtime.deferreturn
。
Reference
Powered by Waline v2.15.2