Defer, Panic, and Recover

Go语言通常有流程控制机制:if,for,switch,goto。还具有go语句在单独的协程中运行代码。讨论一些不太常见的用法:defer,panic以及recover。

defer语句会构建一个函数保存到一个栈中。在函数return后会调用栈中保存的函数。

return并非原子操作,分为赋值,返回值两部分。

return先执行,先将结果写入返回值中也就是赋值;然后defer执行;最后函数携带当前返回值解释函数。

defer

匿名返回值

return时候会创建临时变量保存值

package main

import "fmt"

func main() {
	n := noNameReturn()
	fmt.Println(n)
}

func noNameReturn() int {
	i := 5
	defer func() {
		i++
		fmt.Println("defer 1:", i)
	}()
	defer func() {
		i++
		fmt.Println("defer 2:", i)
	}()
	return i
}

打印:

defer 2: 6
defer 1: 7
5

函数返回值匿名,return会创建个变量比如是v,把i值赋值给这个变量v,并且v和i都是5。之后defer的操作只是影响到了i,没有影响到变量v,因此i加了两次变成了6和7。v没有变还是5,return返回的值就是变量v,返回就是5。

有名返回值

return不会创建额外的变量了,defer会改变这个返回值,操作的都是一个变量。

package main

import "fmt"

func main() {
	n := nameReturn()
	fmt.Println(n)
}

func nameReturn() (i int) {
	i = 5
	defer func() {
		i++
		fmt.Println("defer 1:", i)
	}()
	defer func() {
		i++
		fmt.Println("defer 2:", i)
	}()
	return i
}

打印

defer 2: 6
defer 1: 7
7

操作的都是有名的变量名i,改变i的值会影响函数返回值。

defer栈

Last In First Out 后进先出

package main

import "fmt"

func main() {
	b()
}

func b() {
	for i := 0; i < 4; i++ {
		defer fmt.Println(i)
	}
}

打印

3
2
1
0

panic

panic是内置函数,会停止正常流程。当函数F产生panic的时候,F的执行会被迫停止,defer会正常执行。F函数返回。对于函数F的调用者,也会产生panic(这个panic是F函数里面产生的)。这个过程在程序函数堆栈中执行,向上传递,直到所有的函数都返回,程序崩溃。

产生panic两种方式:主动调用panic;runtime error 比如数组越界。

recover

recover是内置函数可以恢复协程产生的panic。recover只是在defer函数中可用。正常情况下执行recover返回nil并且没有什么副作用。如果当前协程产生了panic,调用recover函数的调用者会截取到给panic的值并恢复正常执行。

demo演示panic和defer

package main

import "fmt"

func main() {
	f()
	fmt.Println("Returned normally from f.")
}

func f() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()
	fmt.Println("Calling g.")
	g(0)
	fmt.Println("Returned normally from g.")
}

func g(i int) {
	if i > 1 {
		fmt.Println("Panicking!")
		panic(fmt.Sprintf("%v", i))
	}
	defer func() {
		fmt.Println("Defer in g", i)
	}()
	fmt.Println("Printing in g", i)
	g(i + 1)
}

打印

Calling g.
Printing in g 0
Printing in g 1
Panicking!
Defer in g 1
Defer in g 0
Recovered in f 2
Returned normally from f.

当进入g函数并且i是2的时候,会产生panic,但是此时defer还没有走到,因此没有执行这个defer,因为没有加入到defer的栈里。只是返回上一层的函数执行defer。直到panic传递到函数f,被recover捕获到,没有走最后一行的fmt.Println("Returned normally from g.")。使得程序正常运行,没有挂掉。

调整下把g里面的defer提上来。

package main

import "fmt"

func main() {
	f()
	fmt.Println("Returned normally from f.")
}

func f() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()
	fmt.Println("Calling g.")
	g(0)
	fmt.Println("Returned normally from g.")
}

func g(i int) {
	defer func() {
		fmt.Println("Defer in g", i)
	}()
	if i > 1 {
		fmt.Println("Panicking!")
		panic(fmt.Sprintf("%v", i))
	}
	fmt.Println("Printing in g", i)
	g(i + 1)
}

打印

Calling g.
Printing in g 0
Printing in g 1
Panicking!
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 2
Returned normally from f.

进入g并且i是2,走到panic那一行的时候,已经走过defer了,猜测已经入栈,因此下面panic产生的时候正常执行defer,会打印出Defer in g 2。之后同上面。

如果删除掉f函数的defer,没有了recover。那么产生的panic会一直到报到栈顶,直到程序挂了,崩溃。

package main

import "fmt"

func main() {
	f()
	fmt.Println("Returned normally from f.")
}

func f() {
	fmt.Println("Calling g.")
	g(0)
	fmt.Println("Returned normally from g.")
}

func g(i int) {
	defer func() {
		fmt.Println("Defer in g", i)
	}()
	if i > 1 {
		fmt.Println("Panicking!")
		panic(fmt.Sprintf("%v", i))
	}
	fmt.Println("Printing in g", i)
	g(i + 1)
}

打印

Calling g.
Printing in g 0
Printing in g 1
Panicking!
Defer in g 2
Defer in g 1
Defer in g 0
panic: 2
        xxx/demo.go:6 +0x19
goroutine 1 [running]:
main.g(0x2)
Process finished with the exit code 2in/go-learn/demo.go:22 +0x199
main.g(0x1)
        xxx/demo.go:25 +0xdf
main.g(0x0)
        xxx/demo.go:25 +0xdf
main.f()
        xxx/demo.go:12 +0x5d
main.main()

可以学习一下go的json库源码处理recover,当是jsonError的时候recover程序并返回异常信息。如果不是jsonError异常认为是程序崩溃了,此处不做recover恢复处理。

其他defer使用场景

有关闭文件

src, err := os.Open(srcName)
    if err != nil {
        return
    }
defer src.Close()

mu.Lock()
defer mu.Unlock()

打印页脚

printHeader()
defer printFooter()

  • 11
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值