• go中的关键字-defer


    1. defer的使用

      defer 延迟调用。我们先来看一下,有defer关键字的代码执行顺序:

    1 func main() {
    2     defer func() {
    3         fmt.Println("1号输出")
    4     }()
    5     defer func() {
    6         fmt.Println("2号输出")
    7     }()
    8 }

      输出结果:

    1 2号出来
    2 1号出来

      结论:多个defer的执行顺序是倒序执行(同入栈先进后出)。

      由例子可以看出来,defer有延迟生效的作用,先使用defer的语句延迟到最后执行。

    1.1 defer与返回值之间的顺序

     1 func defertest() int
     2 
     3 func main() {
     4     fmt.Println("main:", defertest())
     5 }
     6 
     7 func defertest() int {
     8     var i int
     9     defer func() {
    10         i++
    11         fmt.Println("defer2的值:", i)
    12     }()
    13     defer func() {
    14         i++
    15         fmt.Println("defer1的值:", i)
    16     }()
    17     return i
    18 }

      输出结果:

    1 defer1的值: 1
    2 defer2的值: 2
    3 main: 0

      结论:return最先执行->return负责将结果写入返回值中->接着defer开始执行一些收尾工作->最后函数携带当前返回值退出

       return的时候已经先将返回值给定义下来了,就是0,由于i是在函数内部声明所以即使在defer中进行了++操作,也不会影响return的时候做的决定。

     1 func test() (i int)
     2 
     3 func main() {
     4     fmt.Println("main:", test())
     5 }
     6 
     7 func test() (i int) {
     8     defer func() {
     9         i++
    10         fmt.Println("defer2的值:", i)
    11     }()
    12     defer func() {
    13         i++
    14         fmt.Println("defer1的值:", i)
    15     }()
    16     return i
    17 }

      详解:由于返回值提前声明了,所以在return的时候决定的返回值还是0,但是后面两个defer执行后进行了两次++,将i的值变为2,待defer执行完后,函数将i值进行了返回。

    2. defer定义和执行

     1 func test(i *int) int {
     2     return *i
     3 }
     4 
     5 func main(){
     6     var i = 1
     7 
     8     // defer定义的时候test(&i)的值就已经定了,是1,后面就不会变了
     9     defer fmt.Println("i1 ="  , test(&i))
    10     i++
    11 
    12     // defer定义的时候test(&i)的值就已经定了,是2,后面就不会变了
    13     defer fmt.Println("i2 ="  , test(&i))
    14 
    15     // defer定义的时候,i就已经确定了是一个指针类型,地址上的值变了,这里跟着变
    16     defer func(i *int) {
    17         fmt.Println("i3 ="  , *i)
    18     }(&i)
    19 
    20     // defer定义的时候i的值就已经定了,是2,后面就不会变了
    21     defer func(i int) {
    22         //defer 在定义的时候就定了
    23         fmt.Println("i4 ="  , i)
    24     }(i)
    25 
    26     defer func() {
    27         // 地址,所以后续跟着变
    28         var c = &i
    29         fmt.Println("i5 ="  , *c)
    30     }()
    31 
    32     // 执行了 i=11 后才调用,此时i值已是11
    33     defer func() {
    34         fmt.Println("i6 ="  , i)
    35     }()
    36 
    37     i = 11
    38 }

      结论:会先将defer后函数的参数部分的值(或者地址)给先下来【你可以理解为()里头的会先确定】,后面函数执行完,才会执行defer后函数的{}中的逻辑。

    例题分析

     1 //例子1
     2 func f() (result int) {
     3     defer func() {
     4         result++
     5     }()
     6     return 0
     7 }
     8 //例子2
     9 func f() (r int) {
    10      t := 5
    11      defer func() {
    12        t = t + 5
    13      }()
    14      return t
    15 }
    16 //例子3
    17 func f() (r int) {
    18     defer func(r int) {
    19           r = r + 5
    20     }(r)
    21     return 1
    22 }

      例1的正确答案不是0,例2的正确答案不是10,例3的正确答案不是6......

      这里先说一下返回值。defer是在return之前执行的。这条规则毋庸置疑,但最重要的一点是要明白,return xxx这一条语句并不是一条原子指令!

      函数返回的过程:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。defer表达式可能会在设置函数返回值之后,且在返回到调用函数之前去修改返回值,使最终的函数返回值与你想象的不一致。

      return xxx 可被改写成:

    1 返回值 = xxx
    2 调用defer函数
    3 空的return

      所以例子也可以改写成:

     1 //例1
     2 func f() (result int) {
     3      result = 0  //return语句不是一条原子调用,return xxx其实是赋值+ret指令
     4      func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
     5          result++
     6      }()
     7      return
     8 }
     9 //例2
    10 func f() (r int) {
    11      t := 5
    12      r = t //赋值指令
    13      func() {        //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
    14          t = t + 5
    15      }
    16      return        //空的return指令
    17 }
    18 例3
    19 func f() (r int) {
    20      r = 1  //给返回值赋值
    21      func(r int) {        //这里改的r是传值传进去的r,不会改变要返回的那个r值
    22           r = r + 5
    23      }(r)
    24      return        //空的return
    25 }

      所以例1的结果是1,例2的结果是5,例3的结果是1.

    3. defer内部原理

      从例子开始看:

    1 packmage main
    2 
    3 import()
    4 
    5 func main() {
    6   defer println("这是一个测试")
    7 }

      反编译一下看看:

     1 ➜  src $ go build -o test test.go
     2 ➜  src $ go tool objdump -s "main.main" test
    
     1 TEXT main.main(SB) /Users/tushanshan/go/src/test3.go
     2   test3.go:5        0x104ea70        65488b0c2530000000      MOVQ GS:0x30, CX
     3   test3.go:5        0x104ea79        483b6110                CMPQ 0x10(CX), SP
     4   test3.go:5        0x104ea7d        765f                    JBE 0x104eade
     5   test3.go:5        0x104ea7f        4883ec28                SUBQ $0x28, SP
     6   test3.go:5        0x104ea83        48896c2420              MOVQ BP, 0x20(SP)
     7   test3.go:5        0x104ea88        488d6c2420              LEAQ 0x20(SP), BP
     8   test3.go:6        0x104ea8d        c7042410000000          MOVL $0x10, 0(SP)
     9   test3.go:6        0x104ea94        488d05e5290200          LEAQ go.func.*+57(SB), AX
    10   test3.go:6        0x104ea9b        4889442408              MOVQ AX, 0x8(SP)
    11   test3.go:6        0x104eaa0        488d05e6e50100          LEAQ go.string.*+173(SB), AX
    12   test3.go:6        0x104eaa7        4889442410              MOVQ AX, 0x10(SP)
    13   test3.go:6        0x104eaac        48c744241804000000      MOVQ $0x4, 0x18(SP)
    14   test3.go:6        0x104eab5        e8b631fdff              CALL runtime.deferproc(SB)
    15   test3.go:6        0x104eaba        85c0                    TESTL AX, AX
    16   test3.go:6        0x104eabc        7510                    JNE 0x104eace
    17   test3.go:7        0x104eabe        90                      NOPL
    18   test3.go:7        0x104eabf        e83c3afdff              CALL runtime.deferreturn(SB)
    19   test3.go:7        0x104eac4        488b6c2420              MOVQ 0x20(SP), BP
    20   test3.go:7        0x104eac9        4883c428                ADDQ $0x28, SP
    21   test3.go:7        0x104eacd        c3                      RET
    22   test3.go:6        0x104eace        90                      NOPL
    23   test3.go:6        0x104eacf        e82c3afdff              CALL runtime.deferreturn(SB)
    24   test3.go:6        0x104ead4        488b6c2420              MOVQ 0x20(SP), BP
    25   test3.go:6        0x104ead9        4883c428                ADDQ $0x28, SP
    26   test3.go:6        0x104eadd        c3                      RET
    27   test3.go:5        0x104eade        e8cd84ffff              CALL runtime.morestack_noctxt(SB)
    28   test3.go:5        0x104eae3        eb8b                    JMP main.main(SB)
    29   :-1               0x104eae5        cc                      INT $0x3
    30   :-1               0x104eae6        cc                      INT $0x3
    31   :-1               0x104eae7        cc                      INT $0x3

       编译器将defer处理成两个函数调用 deferproc 定义一个延迟调用对象,然后在函数结束前通过 deferreturn 完成最终调用。在defer出现的地方,插入了指令call runtime.deferproc,然后在函数返回之前的地方,插入指令call runtime.deferreturn。

    内部结构

     1 //defer
     2 type _defer struct {
     3    siz     int32   // 参数的大小
     4    started bool    // 是否执行过了
     5    sp      uintptr // sp at time of defer
     6    pc      uintptr
     7    fn      *funcval 
     8    _panic  *_panic // defer中的panic
     9    link    *_defer // defer链表,函数执行流程中的defer,会通过 link这个 属性进行串联
    10 }
    11 //panic
    12 type _panic struct {
    13    argp      unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
    14    arg       interface{}    // argument to panic
    15    link      *_panic        // link to earlier panic
    16    recovered bool           // whether this panic is over
    17    aborted   bool           // the panic was aborted
    18 }
    19 //g
    20 type g struct {
    21    _panic         *_panic // panic组成的链表
    22    _defer         *_defer // defer组成的先进后出的链表,同栈
    23 }

      因为 defer panic 都是绑定在运行的g上的,这里也说一下g中与 defer panic相关的属性

      再把defer, panic, recover放一起看一下:

    1 func main() {
    2     defer func() {
    3         recover()
    4     }()
    5     panic("error")
    6 }

      反编译结果:

    1 go build -gcflags=all="-N -l" main.go
    2 go tool objdump -s "main.main" main
    1 go tool objdump -s "main.main" main | grep CALL
    2   main.go:4             0x4548d0                e81b00fdff              CALL runtime.deferproc(SB)              
    3   main.go:7             0x4548f2                e8b90cfdff              CALL runtime.gopanic(SB)                
    4   main.go:4             0x4548fa                e88108fdff              CALL runtime.deferreturn(SB)            
    5   main.go:3             0x454909                e85282ffff              CALL runtime.morestack_noctxt(SB)       
    6   main.go:5             0x4549a6                e8d511fdff              CALL runtime.gorecover(SB)              
    7   main.go:4             0x4549b5                e8a681ffff              CALL runtime.morestack_noctxt(SB)

      defer 关键字首先会调用 runtime.deferproc 定义一个延迟调用对象,然后再函数结束前,调用 runtime.deferreturn 来完成 defer 定义的函数的调用

      panic 函数就会调用 runtime.gopanic 来实现相关的逻辑

      recover 则调用 runtime.gorecover 来实现 recover 的功能

    deferproc

      根据 defer 关键字后面定义的函数 fn 以及 参数的size,来创建一个延迟执行的 函数,并将这个延迟函数,挂在到当前g的 _defer 的链表上,下面是deferproc的实现:

     1 func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
     2    sp := getcallersp()
     3    argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
     4    callerpc := getcallerpc()
     5    // 获取一个_defer对象, 并放入g._defer链表的头部
     6    d := newdefer(siz)
     7      // 设置defer的fn pc sp等,后面调用
     8    d.fn = fn
     9    d.pc = callerpc
    10    d.sp = sp
    11    switch siz {
    12    case 0:
    13       // Do nothing.
    14    case sys.PtrSize:
    15       // _defer 后面的内存 存储 argp的地址信息
    16       *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
    17    default:
    18       // 如果不是指针类型的参数,把参数拷贝到 _defer 的后面的内存空间
    19       memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
    20    }
    21    return0()
    22 }

      通过newproc 获取一个 _defer 的对象,并加入到当前g的 _defer 链表的头部,然后再把参数或参数的指针拷贝到 获取到的 _defer对象的后面的内存空间。

      再看看newdefer 的实现:

     1 func newdefer(siz int32) *_defer {
     2    var d *_defer
     3    // 根据 size 通过deferclass判断应该分配的 sizeclass,就类似于 内存分配预先确定好几个sizeclass,然后根据size确定sizeclass,找对应的缓存的内存块
     4    sc := deferclass(uintptr(siz))
     5    gp := getg()
     6    // 如果sizeclass在既定的sizeclass范围内,去g绑定的p上找
     7    if sc < uintptr(len(p{}.deferpool)) {
     8       pp := gp.m.p.ptr()
     9       if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {
    10          // 当前sizeclass的缓存数量==0,且不为nil,从sched上获取一批缓存
    11          systemstack(func() {
    12             lock(&sched.deferlock)
    13             for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
    14                d := sched.deferpool[sc]
    15                sched.deferpool[sc] = d.link
    16                d.link = nil
    17                pp.deferpool[sc] = append(pp.deferpool[sc], d)
    18             }
    19             unlock(&sched.deferlock)
    20          })
    21       }
    22       // 如果从sched获取之后,sizeclass对应的缓存不为空,分配
    23       if n := len(pp.deferpool[sc]); n > 0 {
    24          d = pp.deferpool[sc][n-1]
    25          pp.deferpool[sc][n-1] = nil
    26          pp.deferpool[sc] = pp.deferpool[sc][:n-1]
    27       }
    28    }
    29    // p和sched都没有找到 或者 没有对应的sizeclass,直接分配
    30    if d == nil {
    31       // Allocate new defer+args.
    32       systemstack(func() {
    33          total := roundupsize(totaldefersize(uintptr(siz)))
    34          d = (*_defer)(mallocgc(total, deferType, true))
    35       })
    36    }
    37    d.siz = siz
    38    // 插入到g._defer的链表头
    39    d.link = gp._defer
    40    gp._defer = d
    41    return d
    42 }

      newdefer的作用是获取一个_defer对象, 并推入 g._defer链表的头部。根据size获取sizeclass,对sizeclass进行分类缓存,这是内存分配时的思想,先去p上分配,然后批量从全局 sched上获取到本地缓存,这种二级缓存的思想真的在go源码的各个部分都有。

    deferreturn

     1 func deferreturn(arg0 uintptr) {
     2    gp := getg()
     3    // 获取g defer链表的第一个defer,也是最后一个声明的defer
     4    d := gp._defer
     5    // 没有defer,就不需要干什么事了
     6    if d == nil {
     7       return
     8    }
     9    sp := getcallersp()
    10    // 如果defer的sp与callersp不匹配,说明defer不对应,有可能是调用了其他栈帧的延迟函数
    11    if d.sp != sp {
    12       return
    13    }
    14    // 根据d.siz,把原先存储的参数信息获取并存储到arg0里面
    15    switch d.siz {
    16    case 0:
    17       // Do nothing.
    18    case sys.PtrSize:
    19       *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
    20    default:
    21       memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
    22    }
    23    fn := d.fn
    24    d.fn = nil
    25    // defer用过了就释放了,
    26    gp._defer = d.link
    27    freedefer(d)
    28    // 跳转到执行defer
    29    jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
    30 }

    freedefer

      释放defer用到的函数,应该跟调度器、内存分配的思想是一样的。

     1 func freedefer(d *_defer) {
     2    // 判断defer的sizeclass
     3    sc := deferclass(uintptr(d.siz))
     4    // 超出既定的sizeclass范围的话,就是直接分配的内存,那就不管了
     5    if sc >= uintptr(len(p{}.deferpool)) {
     6       return
     7    }
     8    pp := getg().m.p.ptr()
     9    // p本地sizeclass对应的缓冲区满了,批量转移一半到全局sched
    10    if len(pp.deferpool[sc]) == cap(pp.deferpool[sc]) {
    11       // 使用g0来转移
    12       systemstack(func() {
    13          var first, last *_defer
    14          for len(pp.deferpool[sc]) > cap(pp.deferpool[sc])/2 {
    15             n := len(pp.deferpool[sc])
    16             d := pp.deferpool[sc][n-1]
    17             pp.deferpool[sc][n-1] = nil
    18             pp.deferpool[sc] = pp.deferpool[sc][:n-1]
    19             // 先将需要转移的那批defer对象串成一个链表
    20             if first == nil {
    21                first = d
    22             } else {
    23                last.link = d
    24             }
    25             last = d
    26          }
    27          lock(&sched.deferlock)
    28          // 把这个链表放到sched.deferpool对应sizeclass的链表头
    29          last.link = sched.deferpool[sc]
    30          sched.deferpool[sc] = first
    31          unlock(&sched.deferlock)
    32       })
    33    }
    34    // 清空当前要释放的defer的属性
    35    d.siz = 0
    36    d.started = false
    37    d.sp = 0
    38    d.pc = 0
    39    d.link = nil
    40 
    41    pp.deferpool[sc] = append(pp.deferpool[sc], d)
    42 }

    gopanic

     1 func gopanic(e interface{}) {
     2    gp := getg()
     3 
     4    var p _panic
     5    p.arg = e
     6    p.link = gp._panic
     7    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
     8 
     9    atomic.Xadd(&runningPanicDefers, 1)
    10    // 依次执行 g._defer链表的defer对象
    11    for {
    12       d := gp._defer
    13       if d == nil {
    14          break
    15       }
    16 
    17       // If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
    18       // take defer off list. The earlier panic or Goexit will not continue running.
    19       // 正常情况下,defer执行完成之后都会被移除,既然这个defer没有移除,原因只有两种: 1. 这个defer里面引发了panic 2. 这个defer里面引发了 runtime.Goexit,但是这个defer已经执行过了,需要移除,如果引发这个defer没有被移除是第一个原因,那么这个panic也需要移除,因为这个panic也执行过了,这里给panic增加标志位,以待后续移除
    20       if d.started {
    21          if d._panic != nil {
    22             d._panic.aborted = true
    23          }
    24          d._panic = nil
    25          d.fn = nil
    26          gp._defer = d.link
    27          freedefer(d)
    28          continue
    29       }
    30       d.started = true
    31 
    32       // Record the panic that is running the defer.
    33       // If there is a new panic during the deferred call, that panic
    34       // will find d in the list and will mark d._panic (this panic) aborted.
    35       // 把当前的panic 绑定到这个defer上面,defer里面有可能panic,这种情况下就会进入到 上面d.started 的逻辑里面,然后把当前的panic终止掉,因为已经执行过了 
    36       d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
    37       // 执行defer.fn
    38       p.argp = unsafe.Pointer(getargp(0))
    39       reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
    40       p.argp = nil
    41 
    42       // reflectcall did not panic. Remove d.
    43       if gp._defer != d {
    44          throw("bad defer entry in panic")
    45       }
    46       // 解决defer与panic的绑定关系,因为 defer函数已经执行完了,如果有panic或Goexit就不会执行到这里了
    47       d._panic = nil
    48       d.fn = nil
    49       gp._defer = d.link
    50 
    51       // trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
    52       //GC()
    53 
    54       pc := d.pc
    55       sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
    56       freedefer(d)
    57       // panic被recover了,就不需要继续panic了,继续执行剩余的代码
    58       if p.recovered {
    59          atomic.Xadd(&runningPanicDefers, -1)
    60 
    61          gp._panic = p.link
    62          // Aborted panics are marked but remain on the g.panic list.
    63          // Remove them from the list.
    64          // 从panic链表中移除aborted的panic,下面解释
    65          for gp._panic != nil && gp._panic.aborted {
    66             gp._panic = gp._panic.link
    67          }
    68          if gp._panic == nil { // must be done with signal
    69             gp.sig = 0
    70          }
    71          // Pass information about recovering frame to recovery.
    72          gp.sigcode0 = uintptr(sp)
    73          gp.sigcode1 = pc
    74          // 调用recovery, 恢复当前g的调度执行
    75          mcall(recovery)
    76          throw("recovery failed") // mcall should not return
    77       }
    78    }
    79      // 打印panic信息
    80    preprintpanics(gp._panic)
    81      // panic
    82    fatalpanic(gp._panic) // should not return
    83    *(*int)(nil) = 0      // not reached
    84 }

      看下里面gp._panic.aborted 的作用:

     1 func main() {
     2    defer func() { // defer1
     3       recover()
     4    }()
     5    panic1()
     6 }
     7 
     8 func panic1() {
     9    defer func() {  // defer2
    10       panic("error1") // panic2
    11    }()
    12    panic("error")  // panic1
    13 }

      执行顺序详解:

    • 当执行到 panic("error") 时

      g._defer链表: g._defer->defer2->defer1

      g._panic链表:g._panic->panic1 

    • 当执行到 panic("error1") 时 

      g._defer链表: g._defer->defer2->defer1

      g._panic链表:g._panic->panic2->panic1

    • 继续执行到 defer1 函数内部,进行recover()
      此时会去恢复 panic2 引起的 panic, panic2.recovered = true,应该顺着g._panic链表继续处理下一个panic了,但是我们可以发现 panic1 已经执行过了,这也就是下面的代码的逻辑了,去掉已经执行过的panic
    1 for gp._panic != nil && gp._panic.aborted {
    2    gp._panic = gp._panic.link
    3 }

    panic的逻辑:

      程序在遇到panic的时候,就不再继续执行下去了,先把当前panic 挂载到 g._panic 链表上,开始遍历当前g的g._defer链表,然后执行_defer对象定义的函数等,如果 defer函数在调用过程中又发生了 panic,则又执行到了 gopanic函数,最后,循环打印所有panic的信息,并退出当前g。然而,如果调用defer的过程中,遇到了recover,则继续进行调度(mcall(recovery))。

    recovery

     1 func recovery(gp *g) {
     2    // Info about defer passed in G struct.
     3    sp := gp.sigcode0
     4    pc := gp.sigcode1
     5    // Make the deferproc for this d return again,
     6    // this time returning 1.  The calling function will
     7    // jump to the standard return epilogue.
     8    // 记录defer返回的sp pc
     9    gp.sched.sp = sp
    10    gp.sched.pc = pc
    11    gp.sched.lr = 0
    12    gp.sched.ret = 1
    13    // 重新恢复执行调度
    14    gogo(&gp.sched)
    15 }

    gorecover

      gorecovery 仅仅只是设置了 g._panic.recovered 的标志位

     1 func gorecover(argp uintptr) interface{} {
     2    gp := getg()
     3    p := gp._panic
     4    // 需要根据 argp的地址,判断是否在defer函数中被调用
     5    if p != nil && !p.recovered && argp == uintptr(p.argp) {
     6       // 设置标志位,上面gopanic中会对这个标志位做判断
     7       p.recovered = true
     8       return p.arg
     9    }
    10    return nil
    11 }

    goexit

      当手动调用 runtime.Goexit() 退出的时候,defer函数也会执行:

     1 func Goexit() {
     2     // Run all deferred functions for the current goroutine.
     3     // This code is similar to gopanic, see that implementation
     4     // for detailed comments.
     5     gp := getg()
     6   // 遍历defer链表
     7     for {
     8         d := gp._defer
     9         if d == nil {
    10             break
    11         }
    12     // 如果 defer已经执行过了,与defer绑定的panic 终止掉
    13         if d.started {
    14             if d._panic != nil {
    15                 d._panic.aborted = true
    16                 d._panic = nil
    17             }
    18             d.fn = nil
    19       // 从defer链表中移除
    20             gp._defer = d.link
    21       // 释放defer
    22             freedefer(d)
    23             continue
    24         }
    25     // 调用defer内部函数
    26         d.started = true
    27         reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
    28         if gp._defer != d {
    29             throw("bad defer entry in Goexit")
    30         }
    31         d._panic = nil
    32         d.fn = nil
    33         gp._defer = d.link
    34         freedefer(d)
    35         // Note: we ignore recovers here because Goexit isn't a panic
    36     }
    37   // 调用goexit0,清除当前g的属性,重新进入调度
    38     goexit1()
    39 }

  • 相关阅读:
    Oracle启动关闭
    Open_stack 有虚拟机端口不通的问题
    关于Oracle归档的一些操作
    电脑无法开机 接通电源后主板有红灯闪烁的问题
    Centos7+python3.6+face-recognition
    电脑无法开机的问题-主板上有红色告警灯闪烁
    关于systemctl
    Vsftp搭建 for centos7
    外星人入侵——安装Pygame
    mysql索引原理详解
  • 原文地址:https://www.cnblogs.com/33debug/p/11897542.html
Copyright © 2020-2023  润新知