D 的个人博客

全职做开源,自由职业者

  menu

Go 边看边练 -《Go 学习笔记》系列(七)

上一篇: [1438164538421]


ToC


3.5 延迟调用

关键字 defer 用于注册延迟调用。这些调用直到 return 前才被执行,通常 ⽤用于释放资源或错误处理。

1func test() error {
2	f, err := os.Create("test.txt")
3    if err != nil { return err }
4    
5    defer f.Close() // 注册调用,而不是注册函数。必须提供参数,哪怕为空。
6    
7    f.WriteString("Hello, World!")        
8    return nil
9}

多个 defer 注册,按 FILO 次序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

延迟调用参数在注册时求值或复制,可用指针或闭包 "延迟" 读取。

滥用 defer 可能会导致性能问题,尤其是在一个 "大循环" 里。

 1var lock sync.Mutex
 2  	func test() {
 3  	lock.Lock()
 4  	lock.Unlock()
 5}
 6
 7func testdefer() {
 8	lock.Lock()
 9	defer lock.Unlock()
10}
11
12func BenchmarkTest(b *testing.B) {
13	for i := 0; i < b.N; i++ {
14		test()
15	}
16}
17
18func BenchmarkTestDefer(b *testing.B) {
19	for i := 0; i < b.N; i++ {
20		testdefer()
21	}
22}

输出:

1BenchmarkTest" 50000000 43 ns/op
2BenchmarkTestDefer 20000000 128 ns/op

3.6 错误处理

没有结构化异常,使用 panic 抛出错误,recover 捕获错误。

1func test() {
2	defer func() {
3		if err := recover(); err != nil {
4			println(err.(string)) // 将 interface{} 转型为具体类型。
5		}
6	}()
7
8	panic("panic error!")
9}

由于 panicrecover 参数类型为 interface{},因此可抛出任何类型对象。

1func panic(v interface{})
2func recover() interface{}

延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。

 1func test() {
 2	defer func() {
 3		fmt.Println(recover())
 4	}()
 5
 6	defer func() {
 7		panic("defer panic")
 8	}()
 9
10	panic("test panic")
11}
12
13func main() {
14	test()
15}

输出:

1defer panic

捕获函数 recover 只有在延迟调用内直接调用才会终止错误,否则总是返回 nil。任何未捕获的错误都会沿调用堆栈向外传递。

 1func test() {
 2	defer recover() // 无效!
 3	defer fmt.Println(recover()) // 无效!
 4	defer func() {
 5		func() {
 6			println("defer inner")
 7			recover() // 无效!
 8		}()
 9	}()
10    
11	panic("test panic")
12}
13
14func main() {
15	test()
16}

输出:

1defer inner
2<nil>
3panic: test panic

使用延迟匿名函数或下面这样都是有效的。

1func except() {
2	recover()
3}
4
5func test() {
6	defer except()
7	panic("test panic")
8}

如果需要保护代码片段,可将代码块重构成匿名函数,如此可确保后续代码被执行。

 1func test(x, y int) {
 2	var z int
 3	
 4    func() {
 5		defer func() {
 6			if recover() != nil { z = 0 }
 7		}()
 8
 9		z = x / y
10		return
11	}()
12
13	println("x / y =", z)
14}

除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态。

1type error interface {
2	Error() string
3}

标准库 errors.Newfmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。

如何区别使用 panicerror 两种方式?惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error

下一篇: [1438311936449]



社区小贴士

  • 关注标签 [golang] 可以方便查看 Go 相关帖子
  • 关注标签 [Go 学习笔记] 可以方便查看本系列
  • 关注作者后如有新帖将会收到通知

该文章同步自 黑客派