使用环境
| 系统架构 | 语言版本 | |
|---|---|---|
| Amd64 | Go 1.8 |
defer函数
在Go语言中,defer函数用于延迟执行某个函数或语句。它通常被用于处理资源释放、关闭文件句柄等操作,以确保这些操作在函数返回之前被正确执行。
具体来说,当程序遇到一个defer语句时,该语句后面的函数或语句会被放入一个叫做“延迟调用栈”的队列中。当函数执行到return语句时,延迟调用栈中的函数和语句会按照它们被添加到队列中的顺序依次执行。这意味着,即使函数在执行过程中发生了异常或错误,延迟调用栈中的函数和语句仍然会被执行
使用示例
延迟调用
func main() {
defer fmt.Println("defer runs")
fmt.Println("main ends")
}
后进先出
func main() {
defer fmt.Println("defer 1 runs")
defer fmt.Println("defer 2 runs")
defer fmt.Println("defer 3 runs")
fmt.Println("main ends")
}
作用域
func main() {
{
defer fmt.Println("defer runs")
fmt.Println("block ends")
}
fmt.Println("main ends")
}
资源释放
func openFile() {
file, err := os.Open("txt")
if err != nil {
return
}
defer file.Close() //合理位置
}
捕获异常
func demo() {
defer func() {
if err := recover(); err !=nil{
fmt.Println(string(Stack()))
}
}()
panic("unknown")
}
变量处理
func main() {
i := 0
// i变量作为defer函数参数
defer fmt.Println("a:", i)
// 匿名函数调用,捕获同作用域下的i进行计算
defer func() {
fmt.Println("b:", i)
}()
i++
}
原理分析
如果想要函数返回之后继续能执行由defer关键字声明的函数那么必须由一个结构体将defer声明的函数保存起来,每个由defer声明的函数最终会组成一个链表,最新加入的defer函数一直在链表的表头(头插法),所以执行的时候表现为倒序执行
defer结构
type _defer struct {
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 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!
// 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随着Golang版本进行了多次优化,经历了堆上分配、栈上分配、开放编码三种实现方式
堆上分配
- 在
defer语句堆位置插入runtime.deferproc, 在被执行时,延迟调用会被保存为一个_defer记录,并将被延迟调用的入口地址与参数复制保存,存入gorountine的调用链表中。
- 在函数返回之前的位置插入
runtime.deferreturn,当被执行时,会将延迟调用从goroutine链表中取出并执行,多个延迟调用则以jmpdefer尾递归调用方式连续执行。 defer func(){}负责声明,runtime.deferproc负责注册,runtime.deferreturn负责执行。
runtime.deferproc
// 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")
}
// 这里在堆上创建一个defer对象,然后 heap属性设置为true
d := newdefer()
if d._panic != nil {
throw("deferproc: d.panic != nil after newdefer")
}
// 头插法将新的defer对象放在链表头部
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.
}
// 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)
}
// 是否分配在堆上 为true
d.heap = true
return d
}
runtime.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(gp, 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()
}
}
栈上分配
- 和堆上分配相比,负责注册采用了
runtime.deferprocStack函数,runtime.deferreturn依旧负责执行 - 在栈上分配
defer需要将运行时确定的参数赋值给栈上创建的runtime._defer结构体,函数返回后_defer便释放,不需要考虑内存分配时产生的性能开销,只需维护_defer链表即可。
runtime.deferprocStack
//deferprocStack将一个新的延迟函数记录在栈上。
// deferprocStack queues a new deferred function with a defer record on the stack.
//defer记录必须初始化其fn字段。
// The defer record must have its fn field initialized.
// All other fields can contain junk.
// Nosplit because of the uninitialized pointer fields on the stack.
//
//go:nosplit
func deferprocStack(d *_defer) {
gp := getg()
if gp.m.curg != gp {
// go code on the system stack can't defer
throw("defer on system stack")
}
// fn is already set.
// The other fields are junk on entry to deferprocStack and
// are initialized here.
d.started = false
// 是否分配在堆上 为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
// 头插法将新的defer对象放在链表头部
*(*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.
}
runtime.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(gp, 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()
}
}
面试tips
- 题1
func f1() int {
var r int = 6
defer func() {
fmt.Println("defer 中执行")
r *= 7
fmt.Println(r)
}()
return r
}
func main() {
r:= f1()
fmt.Println(r)
}
- 题2
func f2() (r int) {
fmt.Println(&r)
defer func() {
fmt.Println(&r)
fmt.Println("defer 中执行")
r *= 7
fmt.Println(r)
}()
return 6
}
func main() {
r:= f2()
fmt.Println(r)
}
- 题3
func f3() (r int) {
fmt.Println(&r)
defer func(r int) {
fmt.Println(&r)
fmt.Println("defer 中执行")
r *= 7
fmt.Println(r)
}(r)
return 6
}
func main() {
r:= f3()
fmt.Println(r)
}
- 题4
func main(){
panic(" panic~ ")
defer func(){
fmt.Println("执行defer")
}()
}
- 题5
func main(){
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
// 3 3 3
- 题6
for i := 0; i < 3; i++ {
defer func(i int) {
fmt.Println(i)
}(i)
}
// 2 1 0