• 技术文章 >后端开发 >Golang

    在Go中什么是defer?怎么用?

    藏色散人藏色散人2021-09-01 15:52:43转载117
    本文由go语言教程栏目给大家介绍,主题是关于go defer的学习使用,希望对需要的朋友有所帮助!

    什么是defer?

    在Go中,一个函数调用可以跟在一个defer关键字后面,形成一个延迟函数调用。
    当一个函数调用被延迟后,它不会立即被执行。它将被推入由当前协程维护的一个延迟调用堆栈。 当一个函数调用(可能是也可能不是一个延迟调用)返回并进入它的退出阶段后,所有在此函数调用中已经被推入的延迟调用将被按照它们被推入堆栈的顺序逆序执行。 当所有这些延迟调用执行完毕后,此函数调用也就真正退出了。
    举个简单的例子:

    package mainimport "fmt"func sum(a, b int) {
        defer fmt.Println("sum函数即将返回")
        defer fmt.Println("sum函数finished")
        fmt.Printf("参数a=%v,参数b=%v,两数之和为%v\n", a, b, a+b)}func main() {
        sum(1, 2)}

    output:

    参数a=1,参数b=2,两数之和为3
    sum函数finished
    sum函数即将返回

    事实上,每个协程维护着两个调用堆栈。

    defer函数参数估值

    package mainimport  "fmt"func  Print(a int) {fmt.Println("defer函数中a的值=", a)}func  main() {a := 10defer  Print(a)a = 1000fmt.Println("a的值=", a)}

    output:

    a的值= 1000
    defer函数中a的值= 10

    defer Print(a) 被加入到延迟调用堆栈的时候,a 的值是5,故defer Print(a) 输出的结果为5
    例子2:

    package mainimport "fmt"func main() {
        func() {
            for i := 0; i < 3; i++ {
                defer fmt.Println("a=", i)
            }
        }()
    
        fmt.Println()
        func() {
            for i := 0; i < 3; i++ {
                defer func() {
                    fmt.Println("b=", i)
                }()
            }
        }()}

    output:

    a= 2
    a= 1
    a= 0
    
    b= 3
    b= 3
    b= 3

    第一个匿名函数循环中的 i 是在 fmt.Println函数调用被推入延迟调用堆栈的时候估的值,因此输出结果是 2,1,0 , 第二个匿名函数中的 i 是匿名函数调用退出阶段估的值(此时 i 已经变成3了),故结果输出:3,3,3。
    其实对第二个匿名函数调用略加修改,就能使它输出和匿名函数一相同的结果:

    package mainimport "fmt"func main() {
        func() {
            for i := 0; i < 3; i++ {
                defer fmt.Println("a=", i)
            }
        }()
    
        fmt.Println()
        func() {
            for i := 0; i < 3; i++ {
                defer func(i int) {
                    fmt.Println("b=", i)
                }(i)
            }
        }()}

    output:

    a= 2
    a= 1
    a= 0
    
    b= 2
    b= 1
    b= 0

    恐慌(panic)和恢复(defer + recover)

    Go不支持异常抛出和捕获,而是推荐使用返回值显式返回错误。 不过,Go支持一套和异常抛出/捕获类似的机制。此机制称为恐慌/恢复(panic/recover)机制。

    我们可以调用内置函数panic来产生一个恐慌以使当前协程进入恐慌状况。

    进入恐慌状况是另一种使当前函数调用开始返回的途径。 一旦一个函数调用产生一个恐慌,此函数调用将立即进入它的退出阶段,在此函数调用中被推入堆栈的延迟调用将按照它们被推入的顺序逆序执行。

    通过在一个延迟函数调用之中调用内置函数recover,当前协程中的一个恐慌可以被消除,从而使得当前协程重新进入正常状况。

    在一个处于恐慌状况的协程退出之前,其中的恐慌不会蔓延到其它协程。 如果一个协程在恐慌状况下退出,它将使整个程序崩溃。看下面的两个例子:

    package mainimport (
        "fmt"
        "time")func p(a, b int) int {
        return a / b}func main() {
        go func() {
            fmt.Println(p(1, 0))
        }()
        time.Sleep(time.Second)
        fmt.Println("程序正常退出~~~")}

    output:

    panic: runtime error: integer pide by zero
    
    goroutine 6 [running]:
    main.p(...)
            /Users/didi/Desktop/golang/defer.go:9
    main.main.func1()
            /Users/didi/Desktop/golang/defer.go:14 +0x12
    created by main.main
            /Users/didi/Desktop/golang/defer.go:13 +0x39exit status 2

    p函数发生panic(除数为0),因为所在协程没有恐慌恢复机制,导致整个程序崩溃。
    如果p函数所在协程加上恐慌恢复(defer + recover),程序便可正常退出。

    package mainimport (
        "fmt"
        "time")func p(a, b int) int {
        return a / b}func main() {
        go func() {
            defer func() {
                v := recover()
                if v != nil {
                    fmt.Println("恐慌被恢复了:", v)
                }
            }()
            fmt.Println(p(1, 0))
        }()
        time.Sleep(time.Second)
        fmt.Println("程序正常退出~~~")}

    output:

    恐慌被恢复了: runtime error: integer pide by zero
    程序正常退出~~~

    更多golang相关知识,请访问golang教程栏目!

    以上就是在Go中什么是defer?怎么用?的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:learnku,如有侵犯,请联系admin@php.cn删除
    专题推荐:golang
    上一篇:分享优质笔记:go闭包的使用学习 下一篇:避坑啦!Gin安装遇到的坑
    线上培训班

    相关文章推荐

    • 解析Golang怎么创建守护进程和平滑重启• 详解goLang怎么开发windows窗口界面• 你或许听过对Golang map做GC?• 分享golang和vue3开发的一个im应用

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网