1.defer的执行时机
defer的官方定义的为:
包裹defer的函数返回时
包裹defer的函数执行到末尾时
所在的goroutine发生panic时
2.defer 的执行顺序
defer 的执行顺序为LIFO原则,一段程序中后执行的defer代码段会在程序return的时候会优先执行。
defer在匿名返回值和命名返回值函数中的不同表现。
func returnValue() int{
var x int
defer func(){
x ++
fmt.Println("defer")
}
return x
}
func namedReturnValue() (x int){
defer func(){
x ++
fmt.Println("defer")
}
return x
}
观察发现两者都是返回变量x,均在defer中进行自增操作。区别是,上边的x在函数内声明,而下边的x则是在返回值处进行声明。
首先需要了解defer的调用时机,defer在官方问文档说,defer语句在方法返回“时”触发,也就是说return和defer是“同时”发生的,按照匿名返回值方法举例,过程如下:
1.首先将x赋值给返回给返回值,可以将这个返回值理解为GO创建了一个返回值retValue,相当于执行了retValue=result
2.检查收否有defer,若有则执行
3.返回刚刚的返回值。
所以,调用的结果分别为:
0
1
在命名的返回方法中,defer中修改的正是返回的值,而不是那个Go“创建”的retValue,所以返回值被修改了。
3. defer 在for循环中的使用可能会导致性能的问题
如下的代码:
func deferInLoops() {
for i := 0; i < 100; i++ {
f, _ := os.Open("/etc/hosts")
defer f.Close()
}
}
defer在紧邻创建资源的语句后生命力,看上去逻辑没有什么问题。但是和直接调用相比,defer的执行存在着额外的开销,例如defer会对其后需要的参数进行内存拷贝,还需要对defer结构进行压栈出栈操作。所以在循环中定义defer可能导致大量的资源开销,在本例中,可以将f.Close()语句前的defer去掉,来减少大量defer导致的额外资源消耗。
4.应该在判断err之后再执行defer
判断没有err后再执行defer,否则,没有成功获取的资源被defer的释放语句释放则会导致不可预测的错误。
正确写法如下:
resp, err := http.Get(url)
// 先判断操作是否成功
if err != nil {
return err
}
// 如果操作成功,再进行Close操作
defer resp.Body.Close()
- 调用os.Exit()时,
defer
将不会被执行
当发生panic时,所在的goroutine的所有的defer
都不会丢弃,仍会被执行。
但是,当调用os.Exit()方法退出程序的时候,defer
并不会被执行。
例如,下面的例子中的defer
将不会被执行:
func testOsExitDefer(){
defer func(){
fmt.println("this is a test")
}
os.Exit()
}
摘抄自:https://juejin.im/post/5b9b4acde51d450e5071d51f Go语言中defer的一些坑