写这篇博客的目的主要是因为之前在分析golang中互斥锁和读写锁时,发现defer会影响程序运行的效率,并且defer好像还存在很多坑,所以写一篇博客记录一下。
一:defer效率的问题
首先我们用互斥锁进行获取锁和释放锁的过程,看一下加了defer和没加defer的区别。
程序如下:
package main
import (
"fmt"
"sync"
"time"
)
var lock1 sync.Mutex
var lock2 sync.RWMutex
const MAX_NUM = 1e6
func main() {
time1 := time.Now()
for i := 0; i < MAX_NUM; i++ {
test3()
}
time2 := time.Now()
for i := 0; i < MAX_NUM; i++ {
test4()
}
time3 := time.Now()
fmt.Println("lock with defer time:", time2.Sub(time1).String())
fmt.Println("lock without defer time:", time3.Sub(time2).String())
}
func test3() {
lock1.Lock()
defer lock1.Unlock()
}
func test4() {
lock1.Lock()
lock1.Unlock()
}
运行结果如下:
可以看到加上了defer关键字的耗时要是没有加defer关键字的三倍以上。
注意:这里的测试方法存在问题,请到最后看更新部分。
二:使用defer可能会遇到的坑
首先我们要知道一点,defer的执行顺序是在什么位置
看一下下面的代码:
package main
import "fmt"
func main() {
fmt.Println(test(1))
}
func test(i int) (re int) {
defer func() {
i++;
fmt.Println("defer i = ", i)
}()
re = i //defer func未执行 re = 1
return
//defer执行 i自增
}
输出结果:
可以看到defer是在return之后执行的,如果有多个defer呢?
见下面代码:
package main
import "fmt"
func main() {
fmt.Println(test(1))
}
func test(i int) (re int) {
defer func() {
i++;
fmt.Println("defer1 i = ", i)
}()
defer func() {
i++;
fmt.Println("defer2 i = ", i)
}()
re = i //defer func未执行 re = 1
return
//defer执行 i自增
}
输出结果如下:
可以看到后面的defer会先执行,这就类似于把所有defer压入了栈中,先进后出,后入先出。所以在后面的defer反而会先执行。
特别要注意的一点就是:虽然defer是在return后面执行的,但有一种情况我们要十分小心
见下列代码:
package main
import "fmt"
func main() {
fmt.Println(test2())
}
func test2() (re int) {
re = 0
defer func() {
re++
}()
return
}
输出的结果竟然是1,见下图:
正常理解应该是re为0,然后return返回0,之后re再自增,但是最后返回的结果为1是因为return并不是一个原子操作。
这里有一位大佬说的已经很清楚了,我就直接把他的文章贴出来了:https://studygolang.com/articles/742
2019.4.22更新部分
补充:和同事讨论了一个关于defer的影响,同事推荐我用sleep来测试一下defer所消耗的绝对时间,因为之前的测试方法存在问题,到底加不加defer会对时间有几倍的影响完全取决与这个函数所消耗的时间与defer本身消耗时间的比值,下面采用一种更科学的方法来测试。
package main
import (
"fmt"
"sync"
"time"
)
var lock1 sync.Mutex
var lock2 sync.RWMutex
const MAX_NUM = 1e5
func main() {
time1 := time.Now()
for i := 0; i < MAX_NUM; i++ {
test3()
}
time2 := time.Now()
for i := 0; i < MAX_NUM; i++ {
test4()
}
time3 := time.Now()
fmt.Println("lock with defer time:", time2.Sub(time1).String())
fmt.Println("lock without defer time:", time3.Sub(time2).String())
}
func test3() {
//lock1.Lock()
//defer lock1.Unlock()
defer func() {
time.Sleep(time.Microsecond * 1)
//fmt.Print(i)
}()
}
func test4() {
//lock1.Lock()
//lock1.Unlock()
func() {
time.Sleep(time.Microsecond * 1)
//i := 0
//fmt.Print(i)
}()
}
可以看到执行十万次defer需要大概60ms,那么单次defer所消耗的时间也就在微秒级别,其实影响并不算太大,而且甚至其中出现过defer消耗时间更短的情况…
结 论 : d e f e r 对 性 能 的 影 响 不 大 。 \color{#FF0000}{结论:defer对性能的影响不大。} 结论:defer对性能的影响不大。
2019.5.7更新部分
前几天读effective go,关于defer又有一些感悟,比如下面这段程序
package main
import (
"fmt"
)
func test(str string) string {
fmt.Println("in test: ", str)
return str
}
func trace(str string) string {
fmt.Println("entering: ", str)
return str
}
func untrace(str string) string {
fmt.Println("leaving: ", str)
return str
}
func a() {
defer untrace(test(trace("a")))
fmt.Println("in a")
}
func b() {
defer untrace(test(trace("b")))
fmt.Println("in b")
a()
}
func main() {
b()
}
输出竟然是:
entering: b
in test: b
in b
entering: a
in test: a
in a
leaving: a
leaving: b
也就是说在函数嵌套调用的时候,如果前面加了defer;那么defer只会把最外层的函数推迟调用。对于函数内的值,会先求出来,而不是等到最后。