1. 跟在defer后面的函数调用不会立刻执行,像是被注册到了当前函数中,等到当前函数返回之前按照先进后出(First In Last Out)顺序调用所有注册的函数。
  2. defer后出现多次调用,只针对最后那个函数会被延迟。比如 defer fn()(),fn()返回一个闭包函数。
1
2
3
4
5
6
7
// defer fn()()

func fn() func() {
    return func() {
        println("defer")
    }   
}
  1. 被延迟调用的函数的参数会立刻求值。比如 defer close(getChan())
  2. 注册的defer会在以下三个地方被运行:
    • (1)函数返回也就是 return 时,一般情况在deferreturn()函数中。
    • (2)panic() 函数触发时。
    • (3)runtime.Goexit() 函数被调用时。

defer结构

  1. defer存在三种情况:
    • (1)分配 defer 结构体,使用 defer 链表。
    • (2)defer 结构体分配在函数调用上,使用 defer 链表。
    • (3)使用 open-coded defer 形式,不用堆分配也不用 defer 链表。
  2. 但是还是需要一个 _defer 结构体记录相关信息,该链表记录在全局链表后面存储的这个函数注册的所有 defer 信息。
  3. 该链表在 panic 发生是或 runtime.Goexit() 触发时,通过栈扫描形式被追加到 goroutine._panic 后面。
  4. 文件位置:go1.19.3/src/runtime/runtime2.go。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in deferProcStack.
// This struct must match the code in cmd/compile/internal/ssagen/ssa.go:deferstruct
// and cmd/compile/internal/ssagen/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap.
// All defers are logically part of the stack, so write barriers to
// initialize them are not required. All defers must be manually scanned,
// and for heap defers, marked.
type _defer struct {
    // 表示有个 panic 或者 runtime.Goexit() 函数触发开始执行该defer函数。
    started bool    // started和_panic被用于panic发生时,正常这里是 false
    
    // 是否为堆分配,是(true) 否(false)
    //  1. 堆分配则是通过 deferproc() 函数注册的defer链表
    //  2. 栈分配则是通过 deferprocStack() 函数注册defer链表
    heap    bool    // 堆分配时会调用 deferproc() 函数
    
    // 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).
    // 
    // 是否是展开方式	open-coded defer
    // 展开方式的信息记录在fd、varp、framepc中
    openDefer bool
    
    // sp和pc用于在发生 panic() 并且有 recover() 函数恢复的情况下程序需要跳转到哪里,
    // 注意一旦使用这里跳转那么 return0() 函数将返回1表示后面接到执行 deferreturn() 函数。
    // getcallersp()函数获取调用者的SP,也就是调用deferproc()函数之前的SP寄存器的值(调用者函数SP寄存器值),
    // 用于记录当前函数的rsp栈信息,在deferreturn()函数中用于判断defer是否由当前函数注册。
    // 这个值有两个用途:
    //  1. 在deferreturn()函数执行defer函数时用来判断该defer是不是被当前函数注册的。
    // 	2. 在执行recover()函数的时候用来还原栈指针。
    sp        uintptr // sp at time of defer	SP寄存器的值
    // getcallerpc()函数获取调用者指令指针的位置,从调用者视角看来就是CALL runtime.deferproc后面的那条指令的地址
    // 主要用途:在执行recover()函数的时候还原IP寄存器指令指针
    pc        uintptr // pc at time of defer	IP寄存器的值
    
    // open-coded defers 时该值是nil。
    // 注意:在前面1.12版本中fn的类型是*funcval,而这里是func()函数类型,
    // 所有defer后面的不是func()形式的都会在封装一层,形成Function Value形式。
    // 因为存在这样一层封装,旧版的 siz 字段可以丢弃。
    fn        func()  // can be nil for open-coded defers
    
    // 是触发defer函数执行的panic指针,正常流程执行defer时它就是nil
    // _panic的值是在当前goroutine发生panic后,runtime在执行defer函数时,将该指针指向当前的_panic结构
    // 有panic触发时,_panic指向触发的panic,记录当前触发这个defer的panic结构体。
    _panic    *_panic // panic that is running defer 
    // link指针用来指向下一个_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.
    // 
    // 如果 openDefer 为真,则下面的字段记录有关栈帧和具有open-coded defer的关联函数的值
    // 下面字段记录着通过栈扫描形式的所有defer信息,以便执行发生异常是能正确找到所有的defer
    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
}

堆分配

  1. deferproc() 函数用于注册 defer 函数,也是分配的相关函数。
  2. 这种形式注册的 defer 是最慢的。
  3. deferproc() 函数的大致逻辑:
    • 把defer函数的相关数据存储在runtime._defer这个结构中并添加到当前goroutine的defer链表头部。
    • 通过deferproc()函数注册完一个defer函数后,deferproc()函数的返回值是0(该函数并没有返回值,只是通过return0()函数把0写入AX寄存器)。
    • 后面如果发生了panic,又通过该defer函数成功recover(),那么指令指针和栈指针就会恢复到这里设置的pc、sp处,看起来就像刚从runtime.deferproc()函数返回,只不过返回值为1,编译器插入的if语句继而会跳过函数体,仅执行末尾的deferreturn()函数。

deferproc()

  1. 创建一个新的 defer函数 fn,它没有参数和返回值。
  2. 编译器将defer语句转换成对这个函数this调用。
  3. fn是来自defer func()后面的函数其结构是一个funcval指针。
  4. 参数:fn func(),可以理解为defer a(1)这种形式封装成defer func(){a(1)}形式的调用。
  5. 相比于之前的deferproc()函数,这里少了参数的拷贝。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 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()) {
    // 1) 获取当前正在运行的g
    gp := getg()			
    // gp.m.curg记录当前工作线程正在运行的g,如果不相等那说明应该是在g0栈
    if gp.m.curg != gp {	 	
        // go code on the system stack can't defer
        throw("defer on system stack")
    }

    // 2) 获取或者创建一个defer,注意该defer结构体是在【堆】上分配的
    d := newdefer()			
    if d._panic != nil { // 从缓存中获取的存在其他panic触发标记
        throw("deferproc: d.panic != nil after newdefer")
    }
    
    // 3) 比如现在有三个defer需要注册:defer 1, defer 2, defer 3
    // g._defer = 3 通过link形成链表 3 -> 2 -> 1 注册顺序
    // 执行顺序:3 -> 2 -> 1
    // goroutine上_defer始终存储的是最新的_defer结构
    d.link = gp._defer // 记录goroutine上的defer链表信息,gp._defer记录的是链表的最后一个_defer结构体的指针
    gp._defer = d // 当前goroutine链接上defer链表,gp._defer始终记录的是最后一个
    
    // 4) 这里都是func()形式的闭包,不是该形式的会在外层封装一层
    d.fn = fn // 记录当前defer注册函数
    
    // 5) getcallerpc()函数获取调用者指令指针的位置,
    // 从调用者视角看来就是【CALL runtime.deferproc】后面的那条指令的地址
    // 主要用途:在执行recover()函数的时候还原指令指针
    d.pc = getcallerpc() // 记录当前注册defer函数时的下一条指令地址 IP地址
    
    // We must not be preempted between calling getcallersp and
    // storing it to d.sp because getcallersp's result is a
    // uintptr stack pointer.
    // 
    // 6) getcallersp()函数获取调用者的SP,也就是调用deferproc()函数之前的SP寄存器的值
    // 这个值有两个用途:
    //  1. 在deferreturn()函数执行defer函数时用来判断该defer是不是被当前函数注册的
    // 	2. 在执行recover()函数的时候用来还原栈指针
    d.sp = getcallersp() // 记录当前调用函数的rsp栈信息 SP栈顶

    // 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.
    // 
    // 7) deferproc 正常返回 0
    // 一个停止painc的defer函数使 deferproc 返回 1
    // 编译器生成的代码总是检查返回值,如果 deferproc returns != 0 则跳转到函数的末尾
    return0()	// 是否改掉deferreturn函数
    // No code can go here - the C return register has
    // been set and must not be clobbered.
    
    // return0() 函数把返回值0或1写入【AX】寄存器中
}

newdefer()

  1. 该函数用处创建_defer结构体。
  2. 该函数为了避免频繁的堆分配_defer结构体而采用的缓存池
  3. 要释放 defer 需要调用 freedefer() 函数。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 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       // 初始化一个变量,此时为nil
    mp := acquirem()    // 获取当前的M并禁止被抢占
    pp := mp.p.ptr()    // 获取当前的P
    
    // len(pp.deferpool) == 0:当前P上的可用defer结构如果为空
    // sched.deferpool != nil:当前全局sched.deferpool的可用defer不为空
    //  P上的deferpool是切片[]*_defer,
    //  sched上的是链表形式*_defer
    if len(pp.deferpool) == 0 && sched.deferpool != nil {	
        // 把全局的defer拿部分放到P的defer中备用
        lock(&sched.deferlock)  // 锁住sched.deferlock
        // 如果当前P上的长度 < 其容量的一半 并且 全局sched.deferpool存在
        // 这里的循环保证P本地池子的defer大于一半
        for len(pp.deferpool) < cap(pp.deferpool)/2 && sched.deferpool != nil {
            d := sched.deferpool        // 从sched拿到链表信息
            sched.deferpool = d.link    // 把当前链表的上一个返回给sched,这样就去下了最后一个defer结构
            d.link = nil                // 把当前的defer结构link给重置nil,表示无链接到其他defer
            pp.deferpool = append(pp.deferpool, d) // 把取下的这个放入P的defer池子中
        }
        unlock(&sched.deferlock) // 解锁
    }
    
    // 判断当前P中的defer池子是否存在空闲的defer结构
    if n := len(pp.deferpool); n > 0 {
        d = pp.deferpool[n-1]               // 取P最后一个defer结构
        // 这一步是为了帮助GC
        pp.deferpool[n-1] = nil             // 把取下的这个defer从P的空闲池子中重置为nil
        pp.deferpool = pp.deferpool[:n-1]   // 缩短P空闲池的长度
    }
    releasem(mp)        // 取消当前M禁止被抢占锁
    mp, pp = nil, nil   // 帮助GC,清除指针引用

    // 如果上面没有找到空闲的,那么就使用new()函数自己创建一个
    // 注意这里是new()函数在堆上分配了一个_defer结构体
    if d == nil {
        // Allocate new defer.
        d = new(_defer)
    }
    d.heap = true // 标记当前defer是堆分配
    return d
}

return0()

  1. return0() 是一个存根,用于从 deferproc() 返回 0。
  2. 它在 deferproc() 的最后被调用,以通知调用 Go 函数它不应该跳转到 deferreturn()
  3. 异常情况下 return0() 函数会返回 1,此时 GO 就会跳转到执行 deferreturn()
  4. 也就是 panic 发生时 recover() 函数又恢复了,接到执行时这里会返回 1,表示去执行 deferreturn() 函数处理剩下的 defer
1
2
3
4
5
6
// return0 is a stub used to return 0 from deferproc.
// It is called at the very end of deferproc to signal
// the calling Go function that it should not jump
// to deferreturn.
// in asm_*.s
func return0()

栈分配

  1. 相比堆上分配,栈上分配做了一点优化,即把 runtime._defer 结构分配到当前函数的栈帧上。
  2. 很明显这不适合于循环中的 defer,循环中的 defer 仍然需要通过 deferproc() 函数实现,这种优化只适合用于只会执行一次的 defer。
  3. 编译器通过 runtime.deferprocStack() 函数来执行这类 defer 的注册,相比于 runtime.deferproc() 函数,
  4. 少了通过缓冲池或堆分配 _defer 结构的步骤,性能方面还是稍有提升的。

deferprocStack

  1. 该函数用于_defer结构体在函数上分配时,需要把这个_defer追加到当前g_defer上去,以及初始化一些信息。
  2. 该函数的功能和deferproc()函数功能类似,唯一区别就是fn是在调用deferprocStack()函数前就被设置在调用栈上。
  3. deferprocStack() 将一个新的延迟函数与堆栈上的延迟记录进行排队。
  4. runtime._defer 结构中新增了一个 bool 型的字段 heap 来表示是否为堆上分配,对于这种栈上分配的 _defer 结构,
  5. deferreturn() 函数就不会用 freedefer() 函数进行释放了。因为编译器在栈帧上已经把 _defer 结构的某些字段包括后面追加的 fn 参数都准备好了,
  6. 所以 deferprocStack() 函数这里只需为剩余的几个字段赋值,与 deferproc() 函数的逻辑基本一致。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// deferprocStack queues a new deferred function with a defer record on the stack.
// 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()    // 获取当前g
    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.
    //
    // fn在调用deferprocStack()函数前已被设置
    // 其他字段在进入deferprocStack()时是垃圾字段,并在此处初始化
    d.started = false       // 当前defer未开始
    d.heap = false          // 不是堆分配
    d.openDefer = false     // 不是 open-coded defer 形式
    d.sp = getcallersp()    // 记录当前被调用函数的下一条指令处
    d.pc = getcallerpc()    // 记录当前被调用函数的rsp寄存器信息
    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.
    //
    // 下面的行实现:
    //   d.panic = nil
    //   d.fd = nil
    //   d.link = gp._defer
    //   gp._defer = d
    // 但没有书写障碍。前三个是对栈的写入,因此它们不需要写屏障,
    // 而且是对未初始化内存的写入,所以它们不能使用写屏障。
    // 第四次写入不需要写屏障,因为我们显式标记了所有的defer结构,
    // 所以我们不需要用写屏障跟踪指向它们的指针
    // 因为d结构是在栈上分配的,GC会扫描goroutine栈
    *(*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()
    // No code can go here - the C return register has
    // been set and must not be clobbered.
}

open code defer

  1. 编译器直接将 defer 函数注入到函数调用代码中,这样既不使用堆分配_defer也不使用链表链接_defer。
  2. Go1.14 后面的版本支持 open code defer。
  3. Go1.14通过增加一个标识变量df来解决这类问题,用df中的每一位对应标识当前函数中的一个defer函数是否要执行。(8bit位)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func A(i int) {
    defer A1(i, 2*i)
    if (i > 1) {
        defer A2("Hello", "eggo")
    }
    // code to do something
    return
}

func A1(a,b int){
    //......
}

func A2(m,n string){
    //......
}
  1. 经过编译器转换后。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func A(i int){
    var df byte
    //A1的参数
    var a, b int = i, 2*i
    df |= 1

    //A2的参数
    var m,n string = "Hello", "eggo"
    if i > 1 {
        df |= 2
    }
    //code to do something
        
    //判断A2是否要调用
    if df&2 > 0 {
        df = df&^2
        A2(m, n)
    }
    //判断A1是否要调用
    if df&1 > 0 {
        df = df&^1
        A1(a, b)
    }
    return
    //省略部分与recover相关的逻辑
}
  1. Go1.14把defer函数在当前函数内展开并直接调用,这种方式被称为open coded defer,这种方式不仅不用创建_defer结构体,也脱离了defer链表的束缚,不过这种方式依然不适用于循环中的defer,所以1.12版本defer的处理方式是一直保留的。

open code defer 满足的条件

  1. 没有禁用编译器优化,即没有设置 -gcflags "-N"
  2. 函数中存在defer的使用。
  3. 函数内 defer 的数量不超过8个,且返回语句(return)与延迟语句(defer)个数的数量的乘积不超过15。
  4. 没有defer发生在循环语句中。

运行defer

deferreturn()

  1. 开始运行defer链表,该函数在return前被调用。
  2. 该函数是defer在堆上分配和栈上分配时最后return都会调用的函数。
  3. open-coded defer形式正确情况下是不会调用deferreturn()函数,如果调用该函数一定是有panic()或Goexit()发生。因为open-coded defer没有defer链表必须要先从函数栈中回溯找出defer结构内容。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 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.
// 
// deferreturn 函数为调用者的栈帧运行 defer 函数
// 编译器在调用 defer 函数的末尾插入对 deferretrun 函数的调用
func deferreturn() {
    // 因为当前g上的defer存储的是最新的_defer结构
    gp := getg()    // 获取当前的goroutine
    for {
        // 1) 获取goroutine最新的_defer
        
        // 这里决定了defer是倒叙,也就是后面的defer先执行,前面的defer后执行
        // gp._defer -> defer3 -> defer2 -> defer
        d := gp._defer  // 获取当前g后面的defer链表
        if d == nil {   // 没有链表则返回
            return
        }
        
        // 2) 判断 defer 函数注册是否在当前调用 deferreturn 函数的栈中
        // 也就是 deferreturn 函数只能运行当前调用者函数注册的 defer 函数
        
        // 函数的栈SP是在函数开始就分配的,在本函数内该SP值是不会发生变化的
        sp := getcallersp() // 获取当前调用函数的栈帧,在Go中函数的rsp栈是已开始就被预分配的
        
        // 这里的判断条件也表明自己函数注册的defer不能跑其他函数里面取运行
        if d.sp != sp { // 如果和注册的defer不一致说明当前defer不是该函数的,直接返回
            return
        }
        
        // 3) 运行注册的defer函数,并释放_defer对象
        
        // 3.1) open coded defer形式
        // d.openDefer为真,说明当前是从 gopanic 函数的 open coded defers 跳转而来
        if d.openDefer {	
            // 此时d是同一个函数的一群_defer
            // done为true,表示没有recover或其他,这一组defer都执行完。
             // 前面在panic中回溯栈后组装了一组defer函数成_defer结构,其中某个函数的recover生效了,
             // 最后会继续执行deferreturn()函数,会找这里继续执行剩下的defer函数。
            done := runOpenDeferFrame(gp, d) // 去运行这一群defer,相当于循环
            // done defer是否执行完,正常逻辑到这里是没有panic的,因为刚从recover恢复过来接到执行后面的defer
            // 如果上面defer函数中又出现panic则,不会再回到这里了。
            if !done { 	// 只有出现程序逻辑错误这里才会判断为真
                throw("unfinished open-coded defers in deferreturn")
            }
             // 这一组defer运行完了,_defer结构可以释放了。
            gp._defer = d.link // 移除当前defer在g中的链接
            freedefer(d) // 释放当前的defer占用内存
            // If this frame uses open defers, then this
            // must be the only defer record for the
            // frame, so we can just return.
            //
            // 如果此栈帧使用open defers,那么这一定是该栈帧的唯一延迟记录,因此我们可以直接返回
            return
        }

        // 3.2) 堆和栈形式的_defer对象,执行并释放_defer对象
        fn := d.fn              // 获取当前注册的函数
        d.fn = nil              // 帮助GC
        gp._defer = d.link      // 移除当前defer在g中的链接
        freedefer(d)            // 释放当前的defer占用内存
        fn()                    // 执行当前注册的函数
    }
}

freedefer()

  1. 释放defer(针对堆分配情况),该函数用于deferreturn()执行完需要处理_defer结构体的收尾工作。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// Free the given defer.
// The defer cannot be used after this call.
//
// This is nosplit because the incoming defer is in a perilous state.
// It's not on any defer list, so stack copying won't adjust stack
// pointers in it (namely, d.link). Hence, if we were to copy the
// stack, d could then contain a stale pointer.
//
//go:nosplit
func freedefer(d *_defer) {
    d.link = nil            // 设置link为nil
    // After this point we can copy the stack.

    if d._panic != nil {    // 如果当前_panic存在值则抛出异常
        freedeferpanic()    // 直接抛出异常
    }
    if d.fn != nil {        // 如果当前fn存在值则抛出异常
        freedeferfn()       // 直接抛出异常
    }
    if !d.heap {            // 如果当前不是堆分配,直接返回
        return
    }

    // 以下是针对defer结构体是堆分配的情况
    
    mp := acquirem()        // 获取M并禁止当前被抢占
    pp := mp.p.ptr()        // 获取P
    
    // 当前P的本地池满了,需要移除一半到全局池中去
    // pp.deferpool 是一个切片
    if len(pp.deferpool) == cap(pp.deferpool) { // 这里说明P的空闲池满了
        // Transfer half of local cache to the central cache.
        //
        // 将P得一半的defer转移到sched的defer空闲列表中去
        // first:记录第一个_defer
        // last:记录最后一个_defer
        // 通过link形成链表
        var first, last *_defer
        for len(pp.deferpool) > cap(pp.deferpool)/2 {	
            n := len(pp.deferpool)
            d := pp.deferpool[n-1]          // 注意这里的d是局部变量
            pp.deferpool[n-1] = nil         // 帮助GC
            pp.deferpool = pp.deferpool[:n-1]
            if first == nil {
                first = d
            } else {
                last.link = d
            }
            last = d
        }
        lock(&sched.deferlock)      // 加锁
        last.link = sched.deferpool // 把全局的链接到last后面
        sched.deferpool = first     // 把第一个first追加到全局池中
        unlock(&sched.deferlock)    // 解锁
    }

    *d = _defer{}   // 清空当前defer

    pp.deferpool = append(pp.deferpool, d)  // 把d放入P中的空闲defer池中

    releasem(mp)    // 取消M禁止被抢占
    mp, pp = nil, nil
}

runOpenDeferFrame()

  1. 展开形式的defer处理函数,运行注册的defer。
  2. 为开放编码做了特殊的优化,运行时会调用runtime.runOpenDeferFrame()执行活跃的开放编码延迟函数,
  3. 该函数会执行以下的工作:
    • runtime._defer结构体中读取 deferBits、函数 defer 数量等信息;
    • 循环中依次读取函数的地址和参数信息并通过 deferBits 判断该函数是否需要被执行;
    • 调用deferCallSave()需要执行的 defer 函数;
  4. 该函数如果在deferreturn()函数中被调用,那函数里存在的panic()标志没有作用,因为正常流程不会调用deferreturn()函数,只是在panic()runtime.Goexit()需要注意。
  5. 通过返回值来判断目标栈帧上的open coded defer已经完全执行,并且没有recover
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 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.
//
// gp *g 当前的goroutine
// d *_defer 当前g后面的defer链表
func runOpenDeferFrame(gp *g, d *_defer) bool {
    done := true
    // 所有defer信息都存储在funcdata中
    fd := d.fd	

    // readvarintUnsafe 从 fd 开始以 varint 格式读取 uint32,并返回 uint32 和指向 varint 后面字节的指针
    deferBitsOffset, fd := readvarintUnsafe(fd)
    nDefers, fd := readvarintUnsafe(fd) // nDefers注册的defer个数,函数中defer数量
    // 当前执行的defer
    deferBits := *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) // deferBits就是注册函数的记录标志

    // 遍历注册的defer数量
    for i := int(nDefers) - 1; i >= 0; i-- {
        // read the funcdata info for this defer    从defer中读取funcdata信息
        var closureOffset uint32
        closureOffset, fd = readvarintUnsafe(fd) // closureOffset defer注册函数的偏移量
        if deferBits&(1<<i) == 0 { // 判断当前位上是否有defer满足条件的注册
            continue
        }
        
        // 执行的函数和参数
        closure := *(*func())(unsafe.Pointer(d.varp - uintptr(closureOffset))) // 得到注册的函数
        d.fn = closure
        
         // 8bit 0000_0000
        deferBits = deferBits &^ (1 << i) // 清零指定i位上的标志
        *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) = deferBits // 回写内存
        p := d._panic
        // Call the defer. Note that this can change d.varp if
        // the stack moves.
        deferCallSave(p, d.fn) // 调用注册的函数并处理panic相关,fn()里面可能再次发生panic导致这里执行了部分
        if p != nil && p.aborted { // 如果当前defer由panic触发,并且已被中止,则直接退出,这里好像永远为false
            break
        }
        d.fn = nil
        // 如果当前defer由panic触发,并且当前panic被恢复了,应该结束defer继续去执行panic之后的代码
        if d._panic != nil && d._panic.recovered {
            done = deferBits == 0 // 标记defer函数是否已被处理完
            break
        }
    }

    return done
}

deferCallSave()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// deferCallSave calls fn() 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.
//
// This is marked as a wrapper by the compiler so it doesn't appear in
// tracebacks.
func deferCallSave(p *_panic, fn func()) {
    if p != nil {   // 以下参数在fn()中如果存在recover函数时会被用到
        p.argp = unsafe.Pointer(getargp())
        p.pc = getcallerpc()
        p.sp = unsafe.Pointer(getcallersp())
    }
    fn() // 调用注册的defer函数
    if p != nil {   // 以下参数在下一个open defers时被用到
        p.pc = 0
        p.sp = unsafe.Pointer(nil)
    }
}

readvarintUnsafe()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// readvarintUnsafe reads the uint32 in varint format starting at fd, and returns the
// uint32 and a pointer to the byte following the varint.
//
// There is a similar function runtime.readvarint, which takes a slice of bytes,
// rather than an unsafe pointer. These functions are duplicated, because one of
// the two use cases for the functions would get slower if the functions were
// combined.
//
// readvarintUnsafe 从 fd 开始以 varint 格式读取 uint32,并返回 uint32 和指向 varint 后面字节的指针
// 还有一个类似的函数运行时。readvarint,它需要一个字节片,而不是一个不安全的指针。
// 这些功能是重复的,因为如果将这些功能组合起来,两个功能用例中的一个会变慢
func readvarintUnsafe(fd unsafe.Pointer) (uint32, unsafe.Pointer) {
    var r uint32
    var shift int
    for {
        b := *(*uint8)((unsafe.Pointer(fd)))
        fd = add(fd, unsafe.Sizeof(b))
        if b < 128 {
            return r + uint32(b)<<shift, fd
        }
        r += ((uint32(b) &^ 128) << shift)
        shift += 7
        if shift > 28 {
            panic("Bad varint")
        }
    }
}

addOneOpenDeferFrame()

  1. 该函数是发生panic()runtime.Goexit()时回溯栈寻找defer信息函数。
  2. 将最近的一个open coded defer栈帧添加到_defer链表中。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// addOneOpenDeferFrame scans the stack (in gentraceback order, from inner frames to
// outer frames) for the first frame (if any) with open-coded defers. If it finds
// one, it adds a single entry to the defer chain for that frame. The entry added
// represents all the defers in the associated open defer frame, and is sorted in
// order with respect to any non-open-coded defers.
//
// addOneOpenDeferFrame stops (possibly without adding a new entry) if it encounters
// an in-progress open defer entry. An in-progress open defer entry means there has
// been a new panic because of a defer in the associated frame. addOneOpenDeferFrame
// does not add an open defer entry past a started entry, because that started entry
// still needs to finished, and addOneOpenDeferFrame will be called when that started
// entry is completed. The defer removal loop in gopanic() similarly stops at an
// in-progress defer entry. Together, addOneOpenDeferFrame and the defer removal loop
// ensure the invariant that there is no open defer entry further up the stack than
// an in-progress defer, and also that the defer removal loop is guaranteed to remove
// all not-in-progress open defer entries from the defer chain.
//
// If sp is non-nil, addOneOpenDeferFrame starts the stack scan from the frame
// specified by sp. If sp is nil, it uses the sp from the current defer record (which
// has just been finished). Hence, it continues the stack scan from the frame of the
// defer that just finished. It skips any frame that already has a (not-in-progress)
// open-coded _defer record in the defer chain.
//
// Note: All entries of the defer chain (including this new open-coded entry) have
// their pointers (including sp) adjusted properly if the stack moves while
// running deferred functions. Also, it is safe to pass in the sp arg (which is
// the direct result of calling getcallersp()), because all pointer variables
// (including arguments) are adjusted as needed during stack copies.
//
// gp *g 当前goroutine
// pc uintptr 当前发生panic的下一条指令处
// sp unsafe.Pointer 当前发生panic的函数栈顶
func addOneOpenDeferFrame(gp *g, pc uintptr, sp unsafe.Pointer) {
    var prevDefer *_defer	// goroutine的上一个defer结构
    if sp == nil {
        prevDefer = gp._defer
        pc = prevDefer.framepc
        sp = unsafe.Pointer(prevDefer.sp)
    }
    systemstack(func() {
        gentraceback(pc, uintptr(sp), 0, gp, 0, nil, 0x7fffffff,
            func(frame *stkframe, unused unsafe.Pointer) bool {
                if prevDefer != nil && prevDefer.sp == frame.sp {
                    // Skip the frame for the previous defer that
                    // we just finished (and was used to set
                    // where we restarted the stack scan)
                    return true
                }
                f := frame.fn
                fd := funcdata(f, _FUNCDATA_OpenCodedDeferInfo)
                if fd == nil {
                    return true
                }
                // Insert the open defer record in the
                // chain, in order sorted by sp.
                d := gp._defer
                var prev *_defer
                for d != nil {
                    dsp := d.sp
                    if frame.sp < dsp {
                        break
                    }
                    if frame.sp == dsp {
                        if !d.openDefer {
                            throw("duplicated defer entry")
                        }
                        // Don't add any record past an
                        // in-progress defer entry. We don't
                        // need it, and more importantly, we
                        // want to keep the invariant that
                        // there is no open defer entry
                        // passed an in-progress entry (see
                        // header comment).
                        if d.started {
                            return false
                        }
                        return true
                    }
                    prev = d
                    d = d.link
                }
                if frame.fn.deferreturn == 0 {
                    throw("missing deferreturn")
                }

                d1 := newdefer()
                d1.openDefer = true
                d1._panic = nil
                // These are the pc/sp to set after we've
                // run a defer in this frame that did a
                // recover. We return to a special
                // deferreturn that runs any remaining
                // defers and then returns from the
                // function.
                d1.pc = frame.fn.entry() + uintptr(frame.fn.deferreturn)
                d1.varp = frame.varp
                d1.fd = fd
                // Save the SP/PC associated with current frame,
                // so we can continue stack trace later if needed.
                d1.framepc = frame.pc
                d1.sp = frame.sp
                d1.link = d
                if prev == nil {
                    gp._defer = d1
                } else {
                    prev.link = d1
                }
                // Stop stack scanning after adding one open defer record
                return false
            },
            nil, 0)
    })
}

runtime.Goexit()

  1. 立即终止当前goroutine执行,在终止调用它的Goroutine的运行之前会先执行该Goroution中还没有执行的defer语句。
  2. 该函数运行完defer链表注册的函数,直接调用goexit1()函数,该函数直接切换goroutine处理goroutine的后续工作。
  3. Goexit()代码的逻辑与gopanic()函数相似度很高。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Goexit terminates the goroutine that calls it. No other goroutine is affected.
// Goexit runs all deferred calls before terminating the goroutine. Because Goexit
// is not a panic, any recover calls in those deferred functions will return nil.
//
// Calling Goexit from the main goroutine terminates that goroutine
// without func main returning. Since func main has not returned,
// the program continues execution of other goroutines.
// If all other goroutines exit, the program crashes.
func Goexit() {
    // Run all deferred functions for the current goroutine.
    // This code is similar to gopanic, see that implementation
    // for detailed comments.
    gp := getg()

    // Create a panic object for Goexit, so we can recognize when it might be
    // bypassed by a recover().
    // 为 Goexit 创建一个恐慌对象,这样我们就可以识别它何时可能被 recover() 绕过
    var p _panic
    p.goexit = true
    p.link = gp._panic
    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

    addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
    // 运行defer函数
    for {
        d := gp._defer
        if d == nil {
            break
        }
        if d.started {
            if d._panic != nil {
                d._panic.aborted = true
                d._panic = nil
            }
            if !d.openDefer {
                d.fn = nil
                gp._defer = d.link
                freedefer(d)
                continue
            }
        }
        d.started = true
        d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
        if d.openDefer {
            done := runOpenDeferFrame(gp, d)
            if !done {
                // We should always run all defers in the frame,
                // since there is no panic associated with this
                // defer that can be recovered.
                throw("unfinished open-coded defers in Goexit")
            }
            if p.aborted {
                // Since our current defer caused a panic and may
                // have been already freed, just restart scanning
                // for open-coded defers from this frame again.
                addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
            } else {
                addOneOpenDeferFrame(gp, 0, nil)
            }
        } else {
            // Save the pc/sp in deferCallSave(), so we can "recover" back to this
            // loop if necessary.
            deferCallSave(&p, d.fn)
        }
        if p.aborted {
            // We had a recursive panic in the defer d we started, and
            // then did a recover in a defer that was further down the
            // defer chain than d. In the case of an outstanding Goexit,
            // we force the recover to return back to this loop. d will
            // have already been freed if completed, so just continue
            // immediately to the next defer on the chain.
            p.aborted = false
            continue
        }
        if gp._defer != d {
            throw("bad defer entry in Goexit")
        }
        d._panic = nil
        d.fn = nil
        gp._defer = d.link
        freedefer(d)
        // Note: we ignore recovers here because Goexit isn't a panic
    }
    
    // 这里是Goexit()函数关键,处理完defer后直接切换goroutine了。
    goexit1()
}