defer的作用与底层原理
☺️

defer的作用与底层原理

Tags
Golang
defer
Research
Published
October 25, 2024
Author
chaochao
Language
Source

Golang defer

1、作用:

延迟调用

2、特点:

延迟调用,多个defer函数的调用顺序为后进先出

3、注意事项:

无意识构建了闭包函数

4、常见应用场景:

资源释放;异常的捕获和处理

5、协程的多个defer是怎样的结构:

运行的协程的_defer 链表当中
notion image

看一段测试代码:

notion image
 
测试结果输出:
notion image
最后关闭文件
其中我们可以看到后进先出的情况,并且第5行是无意识的用了一个闭包
 
 

底层代码

我们可以在runtime/panic.go下找到newdefer源码
// Each P holds a pool for defers. // 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 }
notion image
 
rumtime/runtime2.go下有一个g,协程的结构
notion image
 
跳转到_defer
notion image
 
我们不需要关心太多,看到这个link就可以,指向下一个defer的地址。
 

那么到底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. }
notion image
也就是每次创建的新的defer,都会放到链表的头部
 

那我们在调用的时候,什么时候去触发我们协程的defer呢?

有另外一个函数,deferreturn
// deferreturn runs deferred functions for the caller's frame. // The compiler inserts a call to this at the end of any // function which calls defer. func deferreturn() { gp := getg() for { d := gp._defer if d == nil { return } sp := getcallersp() if d.sp != sp { return } if d.openDefer { done := runOpenDeferFrame(d) if !done { throw("unfinished open-coded defers in deferreturn") } gp._defer = d.link freedefer(d) // If this frame uses open defers, then this // must be the only defer record for the // frame, so we can just return. return } fn := d.fn d.fn = nil gp._defer = d.link freedefer(d) fn() } }
notion image