Go语言完整解析Go!Go!Go!(一)golang的高级用法(defer, panic/recover)

1,defer的运用(闭包 + defer)

        func double(x int)(result int){
0)方式零: defer
fmt.Println("defer:", x) //defer的是一个语句,不是函数(闭包)
1)方式一: defer func(){result += x}() //首先构造闭包(引用了环境x和result),闭包的引用也在函数内,在result之前将环境中的result和x相加
2)方式二: defer func(i int){result += i}(x) //闭包只引用了环境变量result,同样闭包的引用在函数内(所以要在闭包定义后用 闭包体(入参) 的格式调用)
      x++
      return x
     }
0)double(10)   //结果为10。在执行到defer的时候,语句入栈,此时x的值已经固定
1)double(10) //结果为22。return之时,x的值为11,result等于x的值也是11,之后再执行闭包就变成了22
2)double(10) //结果为21. 在执行到defer语句的时候,闭包加载,此时入参i的值已经确定,即外层的x(10),return之时,i为10,result是11,执行闭包的结果就是21
小小结:return不是一个原子指令,包括 给返回值值赋值 和 真正ret 2步,defer的内容(语句/闭包)的执行被放在二者之间
被defer的函数称为defered函数,整个的执行原理是:
1)根据defer关键字,将defered函数入栈,如果有多个defer语句的话,则递归依次入栈
2)等到执行到return语句时,defered函数依次出栈,执行
3)关于入参的值,是在入栈的时候已经确定
4)如果闭包中引入了外部变量,则原理同一般的闭包
    被defer的语句同理,只是如果引用了某个变量,则入栈的值就是当前值

 

2,panic和recover

一班而言,当程序panic异常了,程序会中断,并立刻执行该gouroutine(main所在的也是一个goroutine)中被defered的内容,即依次出栈

                   随后,程序崩溃系统给出日志信息,包括panic value和堆栈跟踪信息;

有了recover,他会让recover的函数不继续,但正常返回,这样但是整个goroutine还在

func f(x int){
    defer func(){
        if recover() != nil{fmt.Println("before exec defer:recover after panic")}
    }()    
    fmt.Printf("f(%d)\n", x + 0/x)
    f(x-1)
}

func PrintStack()  {
    var buf [4096]byte
    n := runtime.Stack(buf[:],false)   //为什么一定要buf[:]
    os.Stdout.Write(buf[:n])
}

执行:
defer PrintStack()  
f(1)   

结果:
f(1)

goroutine 1 [running]:    ##这一坨的错误信息是PrintStack()函数打印的信息=堆栈信息
main.PrintStack()         ##根据当前的这个打印可以知道,当panic的时候,处于栈顶的是之前defered函数即PrintStack函数,之后才是panic函数,
  C:/Users/HX/go/src/myWork/testFunction.go:54 +0x62 
panic(0x4a6ba0, 0x533490)   ##我就是panic函数,panic函数下面正是即将引起panic的语句
  C:/go1.10/src/runtime/panic.go:505 +0x237
main.f(0x0)
  C:/Users/HX/go/src/myWork/testFunction.go:48 +0xde      ##是的,我就是真正引起panic的的语句
main.f(0x1)
  C:/Users/HX/go/src/myWork/testFunction.go:49 +0xcf
main.testFunction()
  C:/Users/HX/go/src/myWork/testFunction.go:60 +0x4d
main.main()
  C:/Users/HX/go/src/myWork/main.go:4 +0x29

------------------------------------------------------------
panic: runtime error: integer divide by zero    #这一坨为系统直接报出的错误信息=错误原因(panic value) + 堆栈信息

goroutine 1 [running]:
main.f(0x0)
  C:/Users/go/src/myWork/testFunction.go:48 +0xde   #这一行正是执行0/0的那一杨,所以出错了,于是把目前还在栈中的所有内容打印出来,这里就是栈顶
main.f(0x1)
  C:/Users/go/src/myWork/testFunction.go:49 +0xcf
main.testFunction()
  C:/Users/go/src/myWork/testFunction.go:60 +0x4d
main.main()
  C:/Users/go/src/myWork/main.go:4 +0x29

Process finished with exit code 2

 

======如果有recover操作,则只会由PrintStack()函数打印堆栈信息,panic不会===================================================================

 

f(1)
before exec defer:recover after panic   #执行了recover操作
goroutine 1 [running]:
main.PrintStack()
C:/Users/go/src/myWork/testFunction.go:54 +0x62    #这里是打印本身的语句,即PrintStack的打印语句
main.testFunction()
C:/Users/go/src/myWork/testFunction.go:75 +0x4e
main.main()
C:/Users/go/src/myWork/main.go:4 +0x29
hello go, I also remember you!hahah!        #这里显示程序正常执行下去了,已经到结尾
Process finished with exit code 0

 

 

小结:

//runtime/runtime2.go
type _panic struct {
    // 调用defer时入参的指针
    argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
    arg interface{} // argument to panic
    link *_panic // link to earlier panic
    recovered bool // whether this panic is over
    aborted bool // the panic was aborted
}

 

//runtime/panic.go
func gopanic(e interface{}) {
    gp := getg()
    ...
    // 初始化panic
    var p _panic
    ...

    // 遍历 G 的defer链表
    for {
        d := gp._defer
        ...//调用defer后面的函数。如果函数中包含了recover,那么会调用gorecover
        reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
       ...// 已经有recover被调用
        if p.recovered {
            mcall(recovery)
            throw("recovery failed") // mcall should not return
        }
    }

    // 终止程序
    fatalpanic(gp._panic) // should not return
    *(*int)(nil) = 0      // not reached
}

1,首先要明确,go有运行时即runtime在帮我们运行程序,于是程序执行异常的时候,运行时会替我们调用panic函数,对应到运行时层面的函数就是gopanic

           当然,我们可以在用户程序层面直接调用panic,效果一样的

2,然后可以看到,panic函数执行时,会去遍历G的defer列表,于是在异常退出之前先去执行defer函数,这也就是为什么PrintStack函数得以执行,同时此时此刻的堆栈中有defered  函数的信息

3,recover的加入,就是利用了defer的原理,即在退出之前会去执行以下recover,于是recover就得以处理以下堆栈,让当前goroutine不会异常退出,具体来说就是当前的子函数正常return了

          

 

==================================================

类型断言与interface

var testCredential *CredentialReconciler   //CredentialReconciler实现了reconcile.Reconciler接口

func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, error) {
if r == nil { //实际的结果是,r不为空,打印却显示为nil
return nil, fmt.Errorf("must provide a non-nil Reconciler")
}

 

posted @ 2019-12-01 19:00  水鬼子  阅读(996)  评论(0编辑  收藏  举报