go语言defer机制


go语言 defer提供 在函数返回前执行操作的机制
在需要资源回收的场景 方便易用 文件关闭 socket链接资源 数据库 关闭回收等
定义资源的地方 设置好资源的操作 代码放在一起 减小内存泄漏的可能
defer机制虽然好用 但不免费
性能会比直接函数调用差很多
defer机制中返回值求值 是容易出错的地方

性能对比测试


通过一个对锁机制的defer操作来比较性能差异
package main
import (
"sync"
"testing"
)
var (
lock = new(sync.Mutex)
)
func lockTest() {
lock.Lock()
lock.Unlock()
}
func lockDeferTest() {
lock.Lock()
defer lock.Unlock()
}
func BenchmarkTest(b *testing.B) {
for i := 0; i < b.N; i++ {
    lockTest()
}
}
func BenchmarkTestDefer(b *testing.B) {
for i := 0; i < b.N; i++ {
    lockDeferTest()
}
}
运行命令go test -v -test.bench  性能对比测试
BenchmarkTest-8         100000000           18.5 ns/op
BenchmarkTestDefer-8    20000000            56.4 ns/op
Defer版本的lock操作时间消耗几乎是函数直接调用的3倍以上

defer执行顺序和返回值求值


package main
import (
"fmt"
)
func test_unnamed()(int) {
var i int
defer func() {
    i++
    fmt.Println("defer a:", i)
}()
defer func() {
    i++
    fmt.Println("defer b :", i)
}()
return i
}
func test_named()(i int) {
defer func() {
    i++
    fmt.Println("defer c:", i)
}()
defer func() {
    i++
    fmt.Println("defer d :", i)
}()
return i
}

func main() {
fmt.Println("return:", test_unnamed())
fmt.Println("return:", test_named())
}
执行结果
defer b : 1
defer a: 2
return: 0
defer d : 1
defer c: 2
return: 2
同时有多个defer时的执行顺序  go编译器为每个函数维护了一个先进后出的堆栈 每次遇到defer语句就讲执行体封装后压入堆栈中 等到函数返回时 从堆栈中依次出栈执行
所以 “defer b”语句在后 却先调用
关于函数求值问题 可以将test_unnamed函数返回和defer的执行和求值理解为3个步骤:
运行到“return i“语句时 取值当前i值 赋值给test_unnamed返回值 得到函数test的返回值0(因为test_unnamed中只定义了i 并未操作 i保留成初始默认值)
按照先进后出的方式 一次调用defer语句执行 执行真正的test_unnamed 函数返回 ”return“
以上是分析了匿名返回值的情况 具名返回值test_named的情况稍有不同 return 返回了2 而不是0 因为defer函数中对返回值变量i做了修改
由此可见 使用多个defer和defer函数中还需要处理返回值的情况下极容易出问题 使用时需要小心谨慎
defer释放锁
通过defer释放锁(sync.Mutex)是很常见的场景 示例如下:
def GetMapData(key uint32) uint32{
lock.Lock()
defer lock.Unlock()
if v, ok := mapData[key]; ok{
    return v
}
return 0
}
通过defer直接释放锁 在后续的代码逻辑基本可以忘记锁的存在而写代码 但是这种模式就存在一个锁粒度的问题 整个函数都被锁住了
如果lock后面还有很多复杂或者阻塞的逻辑(写日志 访问数据库 从ch读取数据等)
会导致锁的持有时间过大 影响系统的处理性能
此时可以精细控制逻辑函数的分拆 让锁尽量只控制共享资源
抛弃defer自行控制unlock 以免锁粒度过大
defer是一个很强大的机制 尤其是在资源释放的场景特别适用
defer是有性能损耗 过度使用后也会导致逻辑变复杂