likes
comments
collection
share

浅谈Go的延迟调用、异常处理

作者站长头像
站长
· 阅读数 44

在讲Go的延迟地调用defer之前,先聊聊Go的闭包,这是上篇文章函数中没有提到的。

Go闭包

闭包是由函数及其相关引用环境组合而成的实体,用公式表达就是 闭包 = 函数 + 引用环境

百度百科闭包的概念:闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁

package main

import (
    "fmt"
)

func a() func() int {
    i := 0
    b := func() int {
        i++
        fmt.Println(i)
        return i
    }
    return b
}

func main() {
    test := a()
    test() // 1
    test() // 2
    test() // 3
    a() //不会输出i
}

浅谈Go的延迟调用、异常处理

从上述代码中可以看出,函数b是函数a的内嵌函数,函数a负责返回函数b, 那么变量test接收的就是函数b,执行test() 就会输出i的值,第一次为1,第二次为2,第三次为3...

这段代码就很好展示了什么是闭包,函数a外的变量引用了函数a内部的函数b,这就创建了一个闭包。

由于闭包的创建,函数a中的变量i一直存在,每次执行test(),i都会自增1,那么作用就是当执行完函数a之后,GC不会回收函数a所占用的资源,因为函数b执行需要用到函数a中的变量i

延迟调用defer

讲完了闭包的概念,就回到今天的重点内容延迟调用defer,这个会在异常处理中起到很重要的作用。

defer特性

  1. defer是关键字,用于申明延迟调用
  2. 需要等到return前才会执行调用,可以用来清理资源,错误恢复
  3. 多个defer语句,采用栈的的入队方式,先进后出
  4. defer语句的变量,在声明的时候就明确了

defer用途

  1. 关闭文件句柄
  2. 释放锁资源
  3. 数据库、redis连接释放等
package main

import "fmt"

func main() {
    var test [5]struct{}

    for i := range test {
        defer fmt.Println(i)
    }
} 

输出结果如下: 浅谈Go的延迟调用、异常处理

defer碰上闭包

package main

import "fmt"

func main() {
    var test [5]struct{}
    for i := range test {
        defer func() { 
            fmt.Println(i) 
        }()
    }
} 

输出结果如下:

浅谈Go的延迟调用、异常处理

结果并不是像上面那样输出,因为轮到defer执行的时候,变量i已经变成4了,所以输出的全部都是4

如果运行过程中,延迟调用发生错误,程序会继续执行,按照先进后出的顺序执行,看下面的例子

package main

func test(x int) {
    defer println("a")
    defer println("b")

    defer func() {
        println(10 / x) // div0 异常未被捕获,逐步往外传递,最终终止进程。
    }()

    defer println("c")
}

func main() {
    test(0)
} 

浅谈Go的延迟调用、异常处理

异常处理

Go语言处理处理异常,使用panic抛出异常,使用recover接收异常

panic

  1. go内置函数
  2. 遇到panic函数,会终止后面要执行的语句,存在defer会按照顺序执行
  3. 直到整个goroutine退出,显示错误

revocer

  1. go内置函数
  2. 控制一个goroutine的panicking行为,接收panic异常
  3. 只有在defer调用的函数中才能生效

以下为例子演示:

package main

import "fmt"

func test() {
   defer func() {
      if err := recover(); err != nil {  //接收异常
         fmt.Println(err.(string))
         fmt.Println("defer end")
      }
   }()
   fmt.Println("start")
   panic("this is a panic!") //抛出异常
   fmt.Println("end") //执行不到
}

func main() {
   test()
}

浅谈Go的延迟调用、异常处理

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

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

总结

今天简单的学习了Go闭包延迟调用异常处理,还有很多细节的部分,下一步需要继续深入,对于刚入门go语言的我来说,还有许多地方需要学习,有错误的地方欢迎大家指出,共同进步!!

转载自:https://juejin.cn/post/7169562361127403551
评论
请登录