天天看点

golang defer性能和可能会遇到的坑

写这篇博客的目的主要是因为之前在分析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()
}

           

运行结果如下:

golang defer性能和可能会遇到的坑

可以看到加上了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自增
}
           

输出结果:

golang defer性能和可能会遇到的坑

可以看到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自增
}
           

输出结果如下:

golang defer性能和可能会遇到的坑

可以看到后面的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,见下图:

golang defer性能和可能会遇到的坑

正常理解应该是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)
	}()
}

           
golang defer性能和可能会遇到的坑

可以看到执行十万次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只会把最外层的函数推迟调用。对于函数内的值,会先求出来,而不是等到最后。

继续阅读