Go语言中的延迟函数defer示例详解!
前言
大家都知道go语言的defer功能很强大,对于资源管理非常方便,但是如果没用好,也会有陷阱哦。Go语言中延迟函数defer充当着try...catch的重任,使用起来也非常简便,然而在实际应用中,很多gopher并没有真正搞明白defer、return、返回值、panic之间的执行顺序,从而掉进坑中,今天我们就来揭开它的神秘面纱!话不多说了,来一起看看详细的介绍吧。
先来运行下面两段代码:
A.匿名返回值的情况
packagemain import( "fmt" ) funcmain(){ fmt.Println("areturn:",a())//打印结果为areturn:0 } funca()int{ variint deferfunc(){ i++ fmt.Println("adefer2:",i)//打印结果为adefer2:2 }() deferfunc(){ i++ fmt.Println("adefer1:",i)//打印结果为adefer1:1 }() returni }
B.有名返回值的情况
packagemain import( "fmt" ) funcmain(){ fmt.Println("breturn:",b())//打印结果为breturn:2 } funcb()(iint){ deferfunc(){ i++ fmt.Println("bdefer2:",i)//打印结果为bdefer2:2 }() deferfunc(){ i++ fmt.Println("bdefer1:",i)//打印结果为bdefer1:1 }() returni//或者直接return效果相同 }
先来假设出结论(这是正确结论),帮助大家理解原因:
- 多个defer的执行顺序为“后进先出/先进后出”;
- 所有函数在执行RET返回指令之前,都会先检查是否存在defer语句,若存在则先逆序调用defer语句进行收尾工作再退出返回;
- 匿名返回值是在return执行时被声明,有名返回值则是在函数声明的同时被声明,因此在defer语句中只能访问有名返回值,而不能直接访问匿名返回值;
- return其实应该包含前后两个步骤:第一步是给返回值赋值(若为有名返回值则直接赋值,若为匿名返回值则先声明再赋值);第二步是调用RET返回指令并传入返回值,而RET则会检查defer是否存在,若存在就先逆序插播defer语句,最后RET携带返回值退出函数;
因此,defer、return、返回值三者的执行顺序应该是:return最先给返回值赋值;接着defer开始执行一些收尾工作;最后RET指令携带返回值退出函数。
如何解释两种结果的不同:
上面两段代码的返回结果之所以不同,其实从上面的结论中已经很好理解了。
- a()int函数的返回值没有被提前声名,其值来自于其他变量的赋值,而defer中修改的也是其他变量(其实该defer根本无法直接访问到返回值),因此函数退出时返回值并没有被修改。
- b()(iint)函数的返回值被提前声名,这使得defer可以访问该返回值,因此在return赋值返回值i之后,defer调用返回值i并进行了修改,最后致使return调用RET退出函数后的返回值才会是defer修改过的值。
C.下面我们再来看第三个例子,验证上面的结论:
packagemain import( "fmt" ) funcmain(){ c:=c() fmt.Println("creturn:",*c,c)//打印结果为creturn:20xc082008340 } funcc()*int{ variint deferfunc(){ i++ fmt.Println("cdefer2:",i,&i)//打印结果为cdefer2:20xc082008340 }() deferfunc(){ i++ fmt.Println("cdefer1:",i,&i)//打印结果为cdefer1:10xc082008340 }() return&i }
虽然c()int的返回值没有被提前声明,但是由于c()int的返回值是指针变量,那么在return将变量i的地址赋给返回值后,defer再次修改了i在内存中的实际值,因此return调用RET退出函数时返回值虽然依旧是原来的指针地址,但是其指向的内存实际值已经被成功修改了。
即,我们假设的结论是正确的!
D.补充一条,defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体。
packagemain import( "fmt" "time" ) funcmain(){ deferP(time.Now()) time.Sleep(5e9) fmt.Println("main",time.Now()) } funcP(ttime.Time){ fmt.Println("defer",t) fmt.Println("P",time.Now()) } //输出结果: //main2017-08-0114:59:47.547597041+0800CST //defer2017-08-0114:59:42.545136374+0800CST //P2017-08-0114:59:47.548833586+0800CST
E.defer的作用域
- defer只对当前协程有效(main可以看作是主协程);
- 当任意一条(主)协程发生panic时,会执行当前协程中panic之前已声明的defer;
- 在发生panic的(主)协程中,如果没有一个defer调用recover()进行恢复,则会在执行完最后一个已声明的defer后,引发整个进程崩溃;
- 主动调用os.Exit(int)退出进程时,defer将不再被执行。
packagemain import( "errors" "fmt" "time" //"os" ) funcmain(){ e:=errors.New("error") fmt.Println(e) //(3)panic(e)//defer不会执行 //(4)os.Exit(1)//defer不会执行 deferfmt.Println("defer") //(1)gofunc(){panic(e)}()//会导致defer不会执行 //(2)panic(e)//defer会执行 time.Sleep(1e9) fmt.Println("over.") //(5)os.Exit(1)//defer不会执行 }
F.defer表达式的调用顺序是按照先进后出的方式执行
defer表达式会被放入一个类似于栈(stack)的结构,所以调用的顺序是先进后出/后进先出的。
下面这段代码输出的结果是4321而不是1234。
packagemain import( "fmt" ) funcmain(){ deferfmt.Print(1) deferfmt.Print(2) deferfmt.Print(3) deferfmt.Print(4) }
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。