go defer延迟调用详解

您所在的位置:网站首页 go语言defer详解 go defer延迟调用详解

go defer延迟调用详解

2024-07-12 10:38| 来源: 网络整理| 查看: 265

文章目录​​1. 简介​​​​2. 原则​​​​3. defer特性​​​​4. defer用途​​​​5. defer陷阱​​​​6. defer nil 函数​​​​7. 在错误的位置使用 defer(未通过验证)​​ 1. 简介

defer关键字的作用是当外围函数返回之后才执行被推迟的函数。在文件输入输出操作中经常可以见到 defer关键字,因为它使您不必记住何时关闭已打开的文件:defer关键字调用文件关闭函数关闭已打开 的文件时,可以紧靠着文件打开函数之后

2. 原则

defer函数在外围函数返回之后,以后进先出(LIFO)的原则执行。简单点 说,在一个外围函数中有3个defer函数: f1() 最先出现,然后 f2() ,最后 f3() ,当外围函数执行返回之后, f3() 最先被执行,接着是 f2() ,最后是 f1()

3. defer特性关键字 defer 用于注册延迟调用。这些调用直到 return 前才被执。因此,可以用来做资源清理。多个defer语句,按先进后出的方式执行。defer语句中的变量,在defer声明时就决定了。 4. defer用途关闭文件句柄锁资源释放数据库连接释放

go语言 defer go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。 defer 是先进后出 这个很自然,后面的语句会依赖前面的资源,因此如果先前面的资源先释放了,后面的语句就没法执行了

$ cat defer1.go package mainimport "fmt"func main(){ var whatever [5]struct{} for i := range whatever { defer fmt.Println(i)}}[root@localhost defer]# go run defer1.go 43210

defer 碰上闭包

$ cat defer2.go package mainimport "fmt"func main(){ var whatever [5]struct{} for i := range whatever { defer func(){ fmt.Println(i)}()}}[root@localhost defer]# go run defer2.go 44444

其实go说的很清楚,我们一起来看看go spec如何说的

Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.

也就是说函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4.

defer f.Close

这个大家用的都很频繁,但是go语言编程举了一个可能一不小心会犯错的例子.

$ cat defer3.gopackage mainimport "fmt"type Test struct { name string}func (t *Test)Close(){ fmt.Println(t.name," closed")}func main(){ ts :=[]Test{{"a"},{"b"},{"c"}} for _, t := range ts { defer t.Close() }}[root@localhost defer]# go run defer3.go c closedc closedc closed

这个输出并不会像我们预计的输出c b a,而是输出c c c

可是按照前面的go spec中的说明,应该输出c b a才对啊.

那我们换一种方式来调用一下.

$ cat defer4.gopackage mainimport "fmt"type Test struct { name string}func (t *Test)Close(){ fmt.Println(t.name," closed")}func Close(t Test){ t.Close()}func main(){ ts :=[]Test{{"a"},{"b"},{"c"}} for _, t := range ts { defer Close(t) }}[root@localhost defer]# go run defer4.go c closedb closeda closed

这个时候输出的就是c b a 当然,如果你不想多写一个函数,也很简单,可以像下面这样,同样会输出c b a 看似多此一举的声明

$ cat defer5.gopackage mainimport "fmt"type Test struct { name string}func (t *Test)Close(){ fmt.Println(t.name," closed")}func main(){ ts :=[]Test{{"a"},{"b"},{"c"}} for _, t := range ts { t2 := t defer t2.Close() }}[root@localhost defer]# go run defer5.go c closedb closeda closed

通过以上例子,结合

Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.

这句话。可以得出下面的结论:

defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。但是并没有说struct这里的this指针如何处理,通过这个例子可以看出go语言并没有把这个明确写出来的this指针当作参数来看待。

多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

$ cat defer6.gopackage mainfunc test(x int){ defer println("a") defer println("b") defer func(){ println(100/ x)// div0 异常未被捕获,逐步往外传递,最终终止进程。 }() defer println("c")}func main(){ test(0)}[root@localhost defer]# go run defer6.go cbapanic: runtime error: integer divide by zero

goroutine 1 [running]:main.test.func1(0x0) /root/go/defer/defer6.go:6 +0x6fmain.test(0x0) /root/go/defer/defer6.go:9 +0x140main.main() /root/go/defer/defer6.go:11 +0x2aexit status 2

*延迟调用参数在注册时求值或复制,可用指针或闭包 “延迟” 读取。(未通过)

package mainfunc test() func() { x, y :=10,20 defer func(i int){println("defer:", i, y)// y 闭包引用}(x)// x 被复制 x +=10 y +=100println("x =", x,"y =", y)}func main(){ test()}

输出结果:

x =20 y =120defer:10120

*滥用 defer 可能会导致性能问题,尤其是在一个 “大循环” 里。

$ cat defer8.go package mainimport ("fmt""sync""time")var lock sync.Mutexfunc test(){ lock.Lock() lock.Unlock()}func testdefer(){ lock.Lock() defer lock.Unlock()}func main(){ func(){ t1 := time.Now() for i :=0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3