Closed vieyahn2017 closed 1 year ago
https://studygolang.com/articles/5932
go使用Defer的几个场景
yangyangye · 2016-02-03 07:00:06 · 3370 次点击 · 预计阅读时间 3 分钟 · 12分钟之前 开始浏览
这是一个创建于 2016-02-03 07:00:06 的文章,其中的信息可能已经有所发展或是发生改变。
Go 语言中的 defer 语句是 UNIX 之父 Ken Thompson 大神发明的, 是完全正交的设计.
也正因为 Go 语言遵循的是正交的设计, 所以才有了: “少是指数级的多/Less is exponentially more” 的说法. 因为是正交的设计, 最终得到的组合形式是指数级的组合形式.
相反, C++的特性虽然很多, 但是很多不是正交的设计, 而只是简单的特性罗列, 所以C++的很多地方是无法达到指数级的多的组合方式的. 但是学习成本却非常高.
简单的例子就是C++的构造函数和析构函数和C语言的函数和struct完全是互斥的.
defer 在声明时不会立即执行,而是在函数 return 后,再按照 FILO (先进后出)的原则依次执行每一个 defer.
defer一般用于异常处理、释放资源、清理数据、记录日志等。这有点像面向对象语言的析构函数,优雅又简洁,是 Golang 的亮点之一。
https://blog.csdn.net/yimin_tank/article/details/83717449
Defer函数使用的坑 被defer的调用会在包含的函数的末尾执行,而不是包含代码块的末尾。
import "fmt"
func Open(){
fmt.Println("Open")
}
func Close(){
fmt.Println("Close")
}
func main() {
fmt.Println("开始")
for i:=0;i<3;i++{
Open()
fmt.Println("do something")
defer Close()
}
fmt.Println("结束")
}
开始 Open do something Open do something Open do something 结束 Close Close Close 如上代码,本来意图是在for循环中,open之后,再close释放相关资源,但是结果缺并不是想象的那样。因为defer不会在代码块生效,只会在它所在的函数生效。
正确做法是这样:
import "fmt"
func Open(){
fmt.Println("Open")
}
func Close(){
fmt.Println("Close")
}
func main() {
fmt.Println("开始")
for i:=0;i<3;i++{
func(){
Open()
fmt.Println("do something")
defer Close()
}()
}
fmt.Println("结束")
}
开始 Open do something Close Open do something Close Open do something Close 结束
defer官方的解释
Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.
翻译一下:
每次defer语句执行的时候,会把函数“压栈”,函数参数会被拷贝下来;当外层函数(非代码块,如一个for循环)退出时,defer函数按照定义的逆序执行;如果defer执行的函数为nil, 那么会在最终调用函数的产生panic.
总结
defer 语句经常使用于成对的操作,比如打开和关闭,连接和断开,加锁和解锁,即便是再复杂的控制流,资源在任何情况下都能够正确释放。
Defer函数调用参数的求值
Arguments for a deferred function call are evaluated when the defer statement is evaluated (not when the function is actually executing).
package main
import "fmt"
func main() {
var i int = 1
defer fmt.Println("result =>",func() int { return i * 2 }())
i++
fmt.Println(i) // 2
//最终 prints: result => 2 (not ok if you expected 4)
// 证明 被defer的函数的参数会在 defer声明时求值(而不是在函数实际执行时)。
}
多个defer执行顺序
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执。 示例:
package main //必须
import "fmt"
func test(x int) {
result := 100 / x
fmt.Println("result = ", result)
}
func main() {
defer fmt.Println("aaaaaaaaaaaaaaaa")
defer fmt.Println("bbbbbbbbbbbbbbbb")
//调用一个函数,导致内存出问题,除数不能为0
defer test(0)
defer fmt.Println("cccccccccccccccc")
}
执行结果:
//后进先出原则
cccccccccccccccc
bbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaa
panic: runtime error: integer divide by zero
goroutine 1 [running]: main.test(0x0) D:/GoFiles/src/hello_01/main.go:6 +0xca main.main() D:/GoFiles/src/hello_01/main.go:18 +0x15e
defer