拒绝滥用golang defer机制

golang 18-05-21 16:14 4208  

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是有不小的性能损耗,且过度使用后也会导致逻辑变复杂。

东兴哥 2018-05-25 12:03

都是有不同的场景的

Cichreimi 2021-06-02 15:48

<a href=http://vscialisv.com/>cialis for sale online

Cichreimi 2021-08-07 15:06

<a href=http://cialiswwshop.com/>cialis online</a>

inSobre 2022-04-02 16:32

Symcuc Of these patients had an anastomosis of the inferior epigastric artery to the dorsal penile artery dorsal artery arterialization and eight had an anastomosis of the inferior epigastric artery to the dorsal penile vein dorsal vein arterialization. https://oscialipop.com - Cialis Clobetasol 30g With Free Shipping Suomot Infections Keflex Is Used For Rvezyg <a href=https://oscialipop.com>buy cialis generic online</a> Dvrbcd dysthymia Depressed mood that is not as severe as in major depression. Uqbqgq https://oscialipop.com - order cialis