go defer使用
defer 用于延迟调用
- 关键字 defer 用于注册延迟调用。
- 这些调用直到 return 前才被执。因此,可以用来做资源清理。
- 多个defer语句,按先进后出的方式执行(类似于栈)。
- defer语句中的变量,在defer声明时就决定了。
defer的理解
defer和很多语言的finally关键字有点相似,都是在执行完某段代码之后必须会执行到的,finally块即使catch块有return他还是会被执行的。
defer声明了一个在函数返回时需要执行的函数栈,每次声明都是将对应的函数压入栈中,等到return前再去执行。
defer常常被用在关闭句柄、资源回收释放、锁的释放。
func testDefer1(i int) int {
defer func() {
i += 3
fmt.Println(i)
}()
defer func() {
i += 2
fmt.Println(i)
}()
defer func() {
i += 1
fmt.Println(i)
}()
return i
}
testDefer1(1)
上例的打印结果是:
2
4
7
需要小心的是,defer不是编译期压入栈的,而是运行时,因此在defer前return,defer将不会执行:
func testDefer2(num int) {
if num < 10 {
return
}
defer fmt.Println(num)
}
testDefer2(1)
上例不会打印任何东西。
defer不配合闭包的情况,其值在声明时就已经确定,因此需要格外小心:
func testDefer3(num int) {
defer fmt.Println(num)
num += 10
}
testDefer3(1)
上例打印的是1而不是11.
defer在for range中调用存在内存泄漏的风险,这是因为range是值拷贝的,close函数使用指针执行,当defer执行时,循环结束,s的指针指向了最后一个A,因此输出结果将是三个 clsose 3
,如果将a切片的类型改为*A指针、或者将close改为非指针调用这一切都不会发生,而是正常的close 3、close 2、close 1:
type A struct {
name string
}
func (a *A) close() {
fmt.Println("close", a.name)
}
func testDefer4() {
a := []A{
{"1"},
{"2"},
{"3"},
}
for _, s := range a {
defer s.close()
}
}
testDefer4()
在go1.14之前的版本,defer的性能损耗较大,因此要避免循环中大量调用defer。当然现在已经好很多了:
var lock sync.Mutex
func test() {
lock.Lock()
lock.Unlock()
}
func testdefer() {
lock.Lock()
defer lock.Unlock()
}
func testDefer5() {
func() {
t1 := time.Now()
for i := 0; i < 1000000; i++ {
test()
}
elapsed := time.Since(t1)
fmt.Println("test elapsed: ", elapsed)
}()
func() {
t1 := time.Now()
for i := 0; i < 1000000; i++ {
testdefer()
}
elapsed := time.Since(t1)
fmt.Println("testdefer elapsed: ", elapsed)
}()
}
testDefer5()
结果:
test elapsed: 9.2467ms
testdefer elapsed: 11.9323ms
defer 配合命名返回值使用需要小心,如下例,输出的是2而不是1:
func testDefer6() (i int) {
i = 1
defer func() {
fmt.Println(i)
}()
return 2
}
testDefer6()
defer 关闭资源需要注意句柄是否变化:
func testDefer7() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
// 此时关闭的不是 book.txt而是 another-book.txt
fmt.Printf("defer close book.txt err %v\n", err)
}
}()
}
// ..code...
f, err = os.Open("another-book.txt")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("defer close another-book.txt err %v\n", err)
}
}()
}
return nil
}
fmt.Println(testDefer7())
输出:
defer close book.txt err close another-book.txt: file already closed
<nil>
defer中抛出异常,剩下的defer仍然会执行:
// defer中抛出异常,剩下的defer仍然会执行
func testDefer8() {
defer func() {
fmt.Println(123)
}()
defer func() {
panic("err")
}()
}
testDefer8()
123
panic: err
...省略
defer中的闭包变量,如果在循环中会更改,同样有内存泄漏的可能:
func testDefer9() {
var whatever [5]struct{}
for i := range whatever {
defer func() { fmt.Println(i) }()
}
}
testDefer9()
打印的结果将都是4
版权声明
本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途
发布时间:2021年03月10日 20:13:50
备案号:
闽ICP备19015193号-1
关闭特效
评论区#
还没有评论哦,期待您的评论!
引用发言