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")
}