MENU

Go语言 defer 关键字的坑

November 1, 2020 • 技术分享

说明

defer语句

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

举个例子:

func main() {
    fmt.Println("start")
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    fmt.Println("end")
}

输出结果:

start
end
3
2
1

由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

defer执行时机

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:

defer.png

题1

package main

import "fmt"

func f1() int {
    x := 5
    defer func() {
        x++
        x = 0
    }()
    return x
}

func f2() (x int) {
    defer func() {
        x++
    }()
    return 5
}

func f3() (y int) {
    x := 5
    defer func() {
        x++
    }()
    return x
}

func f4() (x int) {
    defer func(x int) {
        x++
    }(x)
    return 5
}

func f5() int {
    var x int
    defer func() {
        x = 2
    }()
    return x
}

func f6() int {
    var x int
    func() {
        x = 2
    }()
    return x
}

func main() {
    fmt.Println(f1())
    fmt.Println(f2())
    fmt.Println(f3())
    fmt.Println(f4())
    fmt.Println(f5())
    fmt.Println(f6())
}

这段代码的执行结果是

f1: 5
f2: 6
f3: 5
f4: 5
f5: 0
f6: 2

f4(局部变量,地址都不同)。

f6匿名函数直接调用。

一句话概括,defer操作的变量如果未与返回值绑定则视为弃用。

如f1, f3,f5都是未绑定,所以直接返回的是声明时的初始值

defer注册要延迟执行的函数时该函数所有的参数都需要确定其值

题2

package main

import "fmt"

func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

func main() {
    fmt.Println("start")
    x := 1
    y := 2
    defer calc("AA", x, calc("A", x, y))
    x = 10
    defer calc("BB", x, calc("B", x, y))
    y = 20
    defer calc("CC", x, calc("C", x, y))
    fmt.Println("end")
}

这段代码的执行结果是

start
A 1 2 3
B 10 2 12
C 10 20 30
end
CC 10 30 40
BB 10 12 22
AA 1 3 4

执行流程如下:

首先说明defer调用的函数calc会先执行内部的调用。

  1. 输出start
  2. 调用defer calc("AA", x, calc("A", x, y))calc("A", x, y), 输出: A 1 2 3
  3. x的值变为10,调用defer calc("BB", x, calc("B", x, y))calc("B", x, y), 输出: B 10 2 12
  4. y的值变为20,调用defer calc("CC", x, calc("C", x, y))calc("C", x, y), 输出: C 10 20 30
  5. 输出end
  6. 逆序调用defer calc("CC", x, calc("C", x, y)),此时应该为calc("CC", x, 30), 输出 CC 10 30 40
  7. 逆序调用defer calc("BB", x, calc("B", x, y)),此时应该为calc("BB", x, 12), 输出 BB 10 12 22
  8. 逆序调用defer calc("AA", x, calc("A", x, y)),**此处x是最开始的1,此时应该为calc("AA", 1, 3), 输出 AA 1 3 4
ArchivesQR Code
QR Code for this page
Tipping QR Code