golang中defer,panic,recover的用法

在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。 通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。

defer特性:在函数返回之前,调用defer函数的操作,简化函数的清理工作。

1、当defer被声明时,其参数就会被实时解析

func a() {
    i := 0
    defer fmt.Println(i) // 输出的是0,因为i=0,已经明确告诉golang在程序退出时执行输出0的操作
    i++
    defer fmt.Println(i) // 输出的是1
    return
}

输出结果是 1 0

2、函数内有多个defer函数,遵循后进先出原则

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
    // 输出结果是 3210,即LIFO。
}

func c() int {
    var i int
 
    defer func() {
        i++
        fmt.Println("a defer2:", i) // 输出结果为 a defer2: 2
    }()
 
    defer func() {
        i++
        fmt.Println("a defer1:", i) // 输出结果为 a defer1: 1
    }()
 
    return i
}

同个函数的defer遵循后进先出

3、defer函数返回值赋值

func d() (result int) {
    defer func() {
        result++
    }()
    return 0
    // 返回值是1,在defer中被更改了
}

func e() (r int) {
    t := 5
    r = t
    defer func() {
        t = t + 5
    }()
    return t
    // 返回值是5,在defer中并没有修改r的值
}

func f() (r int) {
    defer func(r int) {
          r = r + 5
    }(r)
    return 1
}


func g() (r int) {
    defer func(r *int) {
        *r = *r + 5
    }(&r)
    return 1
    // 返回值是6,defer的传入参数是引用类型,取地址操作会改变最终r的值
}

函数的返回值是有可能在defer函数中被更改,本质是return 语句并不是原子指令。

二、panic用法

Panic is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic. The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes. Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses.

panic是内建函数,会中断函数F的正常执行流程,从F函数中跳出来,跳回到F函数的调用者。对于调用者来说, F看起来就是一个panic,所以调用者会继续向上跳出,直到当前goroutine返回。在跳出的过程中,进程会保持这个函数栈。当goroutine退出时,程序会crash。

除了主动调用panic之外,任何运行时错误都会造成panic。

func TestPanic(t *testing.T) {
    panicTest()
}

func panicTest() {
    defer func() { fmt.Println(1) }()
    defer func() { fmt.Println(2) }()
    panic("手动触发异常")
    fmt.Println("触发异常,将无法执行")
}
运行结果如下:
=== RUN   TestPanic
2
1
--- FAIL: TestPanic (0.00s)
panic: 手动触发异常 [recovered]
    panic: 手动触发异常

goroutine 6 [running]:
testing.tRunner.func1.1(0x6ede00, 0x741540)
    E:/Program Files (x86)/go/src/testing/testing.go:1072 +0x310
testing.tRunner.func1(0xc00002b080)

在panic触发异常之前,defer函数还是按顺序输出了,但后续的内容就无法执行了。

三、recover用法

Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

recover也是一个内建函数,用于捕捉程序异常,必须与defer函数配合使用,通常做法是recover返回非nil时,出现错误,用于执行程序清理资源操作。

func TestRecover(t *testing.T) {
    recoverTest()
}

func recoverTest() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("recover捕获到panic")
            fmt.Println(err)
        }
    }()

    fmt.Println("recoverTest运行开始")
    panic("运行出现异常")
}
--运行结果
=== RUN   TestRecover
recoverTest运行开始
recover捕获到panic
运行出现异常
--- PASS: TestRecover (0.00s)
PASS

进程 已完成,退出代码为 0

 

posted @ 2022-01-05 19:05  为之守望  阅读(393)  评论(0编辑  收藏  举报