您好,欢迎来到二三娱乐。
搜索
您的当前位置:首页go中defer大杂烩

go中defer大杂烩

来源:二三娱乐

多个defer的执行顺序

多个defer出现的时候,它会把defer之后的函数压入一个栈中延迟执行,也就是先进后出(LIFO),写在前面的defer会比写在后面的defer调用的晚。下面通过一个示例看一下:

scss
 代码解读
复制代码
func func1(){
    fmt.Println("我是 func1")
}
func func2(){
    fmt.Println("我是 func2")
}
func func3(){
    fmt.Println("我是 func3")
}
func main(){
    defer func1()
    defer func2()
    defer func3()
    fmt.Println("main1")
    fmt.Println("main2")
}

先执行return的函数,得到返回值,再执行defer,最后执行ret返回。

示例:

go
 代码解读
复制代码
package main

import "fmt"

func deferFunc() int {
	fmt.Println("defer func called")
	return 0
}

func returnFunc() int {
	fmt.Println("return func called")
	return 0
}

func returnAndDefer() int {

	defer deferFunc()

	return returnFunc()
}

func main() {
	returnAndDefer()
}

执行结果为:

return func called

defer func called

但当 return、defer 的执行顺序和函数返回值“相遇” 时,又将会产生许多复杂的场景。
在 demo4_2 中,函数使用命名返回值,最终输出结果为 7。其中经历了这几个过程:

(首先)变量 num 作为返回值,初始值为 0;

(其次)随后变量 num 被赋值为 10;

(然后)return 时,变量 num 作为返回值被重新赋值为 2;

(接着)defer 在 return 后执行,拿到变量 num 进行修改,值为 7;

(最后)变量 num 作为返回值,最终函数返回结果为 7;

func demo4_2() (num int) {
num = 10
defer func() {
num += 5
}()
return 2
}

// 7

再来看一个例子。

在 demo4_3 中,函数使用匿名返回值,最终结果输出为 2。其中经历的过程是这样的:

进入函数,此时返回值变量并未创建;

创建变量 num,赋值为 10;

return 时创建函数返回值变量,并赋值为 2;这个返回值变量你可以把它看成匿名变量,或者是变量 a、b、c、d……,但它就不是变量 num;

defer 时,无论怎样修改变量 num,都与函数返回值无关;

所以,最终的函数返回结果为 2;

func demo4_3() int {
num := 10
defer func() {
num += 5
}()
return 2
}
// 2

defer 最大的功能是 panic 后依然有效,所以defer可以保证你的一些资源一定会被关闭,从而避免一些异常出现的问题。

go
 代码解读
复制代码
package main

import "fmt"

func main() {

	deferPanic()
}

func deferPanic() {

	defer fmt.Println("defer 1")
	defer fmt.Println("defer 2")
	defer fmt.Println("defer 3")

	panic("出错啦")
}

执行输出如下:

defer 3

defer 2

defer 1

panic: 出错啦

defer错误处理

发生 panic 时,已声明的 defer 会出栈执行

运行 demo5_1,可以看到当出现 panic 时,会触发已经声明的 defer 出栈执行,随后在再 panic,而在 panic 之后声明的 defer 将得不到执行。

func demo5_1() {
 defer fmt.Println(1)
 defer fmt.Println(2)
 defer fmt.Println(3)

 panic("没点赞异常") // 触发defer出栈执行

 defer fmt.Println(4) // 得不到执行
}

正是利用这个特性,在 defer 中可以通过 recover 捕获 panic,防止程序崩溃。

func demo5_2() {
 defer func() {
     if err := recover(); err != nil {
         fmt.Println(err, "问题不大")
     }
 }()

 panic("没点赞异常") // 触发defer出栈执行

 // ...
}

panic:

    1、内置函数
    2、假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
    3、返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
    4、直到goroutine整个退出,并报告错误

recover:

    1、内置函数
    2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
    3、一般的调用建议
        a). 在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
        b). 可以获取通过panic传递的error

1.利用recover处理panic指令,defer 必须放在 panic 之前定义,(在panic之后定义的defer不会执行),另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
2.recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
3.多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。

package main

func main() {
    test()
}

func test() {
    defer func() {
        if err := recover(); err != nil {
            println(err.(string)) // 将 interface{} 转型为具体类型。
        }
    }()

    panic("panic error!")
}

defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered:", r)
		}
	}()

	panic("oh no!") // 触发 panic
	fmt.Println("This line will not be executed.")

recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。

package main

import (
	"fmt"
)

func test() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered:", r)//4
		}
		fmt.Println("Returned normalily from main.")//5
	}()

	fmt.Println("Starting main function.")//1
	panicExample()

}

func panicExample() {
	defer fmt.Println("Deferred call in panicExample.")
	fmt.Println("Starting panicExample.")//2
	panic("Oh no! Something went wrong.")//3
	fmt.Println("This line will not be executed.")
}

func main() {
	test()
	fmt.Println("This line will not be executed.")//6
}

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

package main

import "fmt"

func test() {
    defer func() {
        fmt.Println(recover()) //有效
    }()
    defer recover()              //无效!
    defer fmt.Println(recover()) //无效!
    defer func() {
        func() {
            println("defer inner")
            recover() //无效!
        }()
    }()

    panic("test panic")
}

func main() {
    test()
}

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

package main

import (
    "fmt"
)

func except() {
    fmt.Println(recover())
}

func test() {
    defer except()
    panic("test panic")
}

func main() {
    test()
}

Go实现类似 try catch 的异常处理

package main

import "fmt"

func Try(fun func(), handler func(interface{})) {
    defer func() {
        if err := recover(); err != nil {
            handler(err)
        }
    }()
    fun()
}

func main() {
    Try(func() {
        panic("test panic")
    }, func(err interface{}) {
        fmt.Println(err)
    })
}

输出结果:

    test panic

如何区别使用 panic 和 error 两种方式?

惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error。

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

package main

import (
    "errors"
    "fmt"
)

var ErrDivByZero = errors.New("division by zero")

func div(x, y int) (int, error) {
    if y == 0 {
        return 0, ErrDivByZero
    }
    return x / y, nil
}

func main() {
    defer func() {
        fmt.Println(recover())
    }()
    switch z, err := div(10, 0); err {
    case nil:
        println(z)
    case ErrDivByZero:
        panic(err)
    }
}

输出结果:

    division by zero

defer与闭包

package main

import (
    "errors"
    "fmt"
)

func foo(a, b int) (i int, err error) {
    defer fmt.Printf("first defer err %v\n", err)//这里的err获取时还为空
    defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err)//这里的err获取时还为空
  //只有这个是闭包
    defer func() { fmt.Printf("third defer err %v\n", err) }()
    if b == 0 {
        err = errors.New("divided by zero!")
        return
    }

    i = a / b
    return
}

func main() {
    foo(2, 0)
}

输出结果:

    third defer err divided by zero!
    second defer err <nil>
    first defer err <nil>

解释:如果 defer 后面跟的不是一个 closure 最后执行的时候我们得到的并不是最新的值。

defer 在声明时,就已经确认了形参 n 的值,而不是在执行时确认的;所以,后续变量 num 无论如何改变都不影响 defer 的输出结果。

func demo1() {
    for i := 0; i < 5; i++ {
        defer fmt.Println("defer:", i)
    }
}
//没形成闭包,defer压栈时直接传入i值
// defer: 4
// defer: 3
// defer: 2
// defer: 1
// defer: 0

释放两种资源

package main

import (
    "fmt"
    "io"
    "os"
)

func do() error {
    f, err := os.Open("book.txt")
    if err != nil {
        return err
    }
    if f != nil {
        defer func(f io.Closer) {
            if err := f.Close(); err != nil {
                fmt.Printf("defer close book.txt err %v\n", err)
            }
        }(f)
    }

    // ..code...

    f, err = os.Open("another-book.txt")
    if err != nil {
        return err
    }
    if f != nil {
        defer func(f io.Closer) {
            if err := f.Close(); err != nil {
                fmt.Printf("defer close another-book.txt err %v\n", err)
            }
        }(f)
    }

    return nil
}

func main() {
    do()
}


defer下的函数参数包含子函数

javascript
 代码解读
复制代码
package main
 
import "fmt"
 
func function(index int, value int) int {
 
    fmt.Println(index)
 
    return index
}
 
func main() {
    defer function(1, function(3, 0))
    defer function(2, function(4, 0))

这个程序的执行结果是怎么样的的?

3

4

2

1

总结

参考:

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- yule263.com 版权所有 湘ICP备2023023988号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务