22.1 defer的运行机制
- 在Go中,只有在函数和方法内部才能使用defer。
- defer关键字后面只能接函数或方法,这些函数被成为deferred函数。defer将它们注册到其所在goroutine用于存放deferred函数的栈数据结构中。
- 在执行defer的函数退出前,按后进先出(LIFO)的顺序调度执行。
22.2 defer的常见用法
最基本、最常见:用于释放资源
1.拦截panic
按需要对panic进行处理,可以尝试从panic中回复(也是Go语言中唯一的从panic中恢复的手段),也可以触发一个新的panic,但为panic传一个新的error值
// $GOROOT/src/bytes/buffer.go
func makeSlice(n int) []byte {
// If the make fails, give a known error.
defer func() {
if recover() != nil {
panic(ErrTooLarge)
}
}()
return make([]byte, n)
}
deferred函数虽然可以拦截绝大部分的panic,但无法拦截并恢复一些运行时之外的致命问题,比如通过C代码“制造”的崩溃。
2.修改函数的具名返回值
// $GOROOT/src/fmt/scan.go
func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
defer func() {
if e := recover(); e != nil {
if se, ok := e.(scanError); ok {
err = se.err
} else {
panic(e)
}
}
}()
...
}
// $GOROOT/src/net/ipsock_plan9.go
func dialPlan9(ctx context.Context, net string, laddr, raddr Addr) (fd *netFD, err error) {
defer func() { fixErr(err) }()
...
}
// 再来一个更直观的例子
func foo(a, b int) (x, y int) {
defer func() {
x = x * 5
y = y * 10
}()
x = a + 5
y = b + 6
return
}
3.输出调试信息
// $GOROOT/src/net/conf.go
func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder) {
if c.dnsDebugLevel > 1 {
defer func() {
print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
}()
}
...
}
在出入函数时打印留痕日志(一般在debug日志级别下)
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a"))
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}
func main() {
b()
}
4.还原变量旧值
// $GOROOT/src/syscall/fs_nacl.go
func init() {
oldFsinit := fsinit
defer func() { fsinit = oldFsinit }()
fsinit = func() {}
...
}
先将fsinit存储在局部变量oldFsinit中,然后在deferred函数将fsinit的值重新置为存储在oldFsinit中的旧值。
22.3 关于defer的几个关键问题
1.明确哪些函数可以作为deferred函数
内置函数append/cap/complex/imag/len/make/new/real不可以直接作为deferred函数
func bar() (int, int) {
return 1, 2
}
func fooD() {
// builtin functions:
// append cap close complex copy delete imag len
// make new panic print println real recover
var c chan int
var sl []int
var m = make(map[string]int, 10)
m["item1"] = 1
m["item2"] = 2
var a = complex(1.0, -1.4)
var sl1 []int
defer bar()
//defer append(sl, 11)
//defer cap(sl)
defer close(c)
//defer complex(2, -2)
defer copy(sl1, sl)
defer delete(m, "item2")
//defer imag(a)
//defer len(sl)
//defer make([]int, 10)
//defer new(*int)
defer panic(1)
defer print("hello, defer\n")
defer println("hello, defer")
//defer real(a)
defer recover()
}
- 对于有返回值的自定义函数或方法,返回值会在deferred函数被调用执行的时候被自动丢弃
- 对于哪些不能直接作为deferred函数的内置函数,可以使用一个包裹不能直接作为deferred函数的匿名函数来间接满足要求(意义不大)
defer func() {
_ = append(s1, 11)
}
2.把握好defer关键字后表达式的求值时机
defer关键字后面的表达式是在将deferred函数注册到deferred函数栈的时候进行求值的。
例子1
- 函数&带参数的匿名函数,传入for循环每次i的值
- 不带参数的匿名函数,传入for循环结束后i=4
func foo1() {
for i := 0; i <= 3; i++ {
defer fmt.Println(i)
}
}
func foo2() {
for i := 0; i <= 3; i++ {
defer func(n int) {
fmt.Println(n)
}(i)
}
}
func foo3() {
for i := 0; i <= 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
func main() {
fmt.Println("foo1 result:")
foo1() //3 2 1 0
fmt.Println("\nfoo2 result:")
foo2() //3 2 1 0
fmt.Println("\nfoo3 result:")
foo3() //4 4 4 4
}
例子2
- foo11:传入的变量s1的值为
[]int{1,2,3}
,因此压入deferred函数栈的函数是func([]int{1,2,3})
- foo22:传入的变量为s1的地址,因此压入deferred函数栈的函数是
func(&s1)
func foo11() {
sl := []int{1, 2, 3}
defer func(a []int) {
fmt.Println(a)
}(sl)
sl = []int{3, 2, 1}
_ = sl
}
func foo22() {
sl := []int{1, 2, 3}
defer func(p *[]int) {
fmt.Println(*p)
}(&sl)
sl = []int{3, 2, 1}
_ = sl
}
func main() {
foo11() // [1 2 3]
foo22() // [3 2 1]
}
3.知晓defer带来的性能损耗
Go1.14版本中,defer性能提升巨大,已经和不使用defer的性能相差很小了
标签:defer,函数,22,读书笔记,int,deferred,func,sl From: https://www.cnblogs.com/brynchen/p/18014424