zoukankan      html  css  js  c++  java
  • golang panic和defer

    一、参考

    panic 函数、recover 函数以及 defer 语句

    二、panic

    2.1 具体报错

    
    package main
    
    import "fmt"
    
    func main() {
    
    	s := []int64{1, 2, 3, 4}
    	fmt.Println(s[4])
    
    }
    
    
    

    (1) 第一行是

    panic: runtime error: index out of range,

    其中的“runtime error”的含义是,这是一个 runtime 代码包中抛出的 panic

    此详情中的“panic:”右边的内容,正是这个 panic 包含的 runtime.Error 类型值的字符串表示形式

    (2) panic 详情中,一般还会包含与它的引发原因有关的 goroutine 的代码执行信息。正如前述详情中的“goroutine 1 [running]”,它表示有一个 ID 为 1 的 goroutine 在此 panic 被引发的时候正在运行

    注意,这里的 ID 其实并不重要,因为它只是 Go 语言运行时系统内部给予的一个 goroutine 编号,我们在程序中是无法获取和更改的

    (3) “main.main()”表明了这个 goroutine 包装的 go 函数就是命令源码文件中的那个 main 函数,也就是说这里的 goroutine 正是主 goroutine

    (4) 再下面的一行,指出的就是这个 goroutine 中的哪一行代码在此 panic 被引发时正在执行

    这一行最后的 +0x1b 代表的是:此行代码相对于其所属函数的入口程序计数偏移量。不过,一般情况下它的用处并不大

    (5) 最后,“exit status 2”表明我的这个程序是以退出状态码 2 结束运行的

    在 Go 语言中,因 panic 导致程序结束运行的退出状态码一般都会是 2

    在大多数操作系统中,只要退出状态码不是 0,都意味着程序运行的非正常结束

    2.2 panic 过程

    从 panic 被引发到程序终止运行的大致过程是什么?

    (1) 某个函数中的某行代码有意或无意地引发了一个 panic, 初始的 panic 详情会被建立起来

    (2) 该程序的控制权会立即从此行代码转移至调用其所属函数的那行代码上,也就是调用栈中的上一级

    意味着,此行代码所属函数的执行随即终止

    (3) 控制权并不会在此有片刻的停留,它又会立即转移至再上一级的调用代码处

    控制权如此一级一级地沿着调用栈的反方向传播至顶端,也就是我们编写的最外层函数那里

    这里的最外层函数指的是 go 函数,对于主 goroutine 来说就是 main 函数

    (4) 但是控制权也不会停留在那里,而是被 Go 语言运行时系统收回

    (5) 随后,程序崩溃并终止运行,承载程序这次运行的进程也会随之死亡并消失

    在这个控制权传播的过程中,panic 详情会被逐渐地积累和完善,并会在程序终止之前被打印出来

    比如,

    main 函数调用了 caller1 函数,

    而 caller1 函数又调用了 caller2 函数,

    那么 caller2 函数中代码的执行信息会先出现,

    然后是 caller1 函数中代码的执行信息,

    最后才是 main 函数的信息

    2.3 error 和 panic

    Go 语言的内建函数 panic 是专门用于引发 panic 的。panic 函数使程序开发者可以在程序运行期间报告异常

    注意,这与从函数返回错误值的意义是完全不同的

    (1) error

    当我们的函数返回一个非 nil 的错误值时,函数的调用方有权选择不处理,并且不处理的后果往往是不致命的, 不至于使程序无法提供任何功能(也可以说僵死)或者直接崩溃并终止运行(也就是真死)

    (2) panic

    当一个 panic 发生时,如果我们不施加任何保护措施,那么导致的直接后果就是程序崩溃,就像前面描述的那样,这显然是致命的

    2.4 构造 panic

    
    package main
    
    func main() {
    
    	panic("test panic")
    
    }
    
    // 运行结果
    
    ➜  others git:(master) ✗ go run main.go
    panic: test panic
    
    goroutine 1 [running]:
    main.main()
            /Users/yz/work/gitee/go-pl/others/main.go:5 +0x39
    exit status 2
    
    

    在调用 panic 函数时,把某个值作为参数传给该函数就可以了。

    由于 panic 函数的唯一一个参数是空接口(也就是 interface{})类型的,所以从语法上讲,它可以接受任何类型的值。

    我们最好传入 error 类型的错误值,或者其他的可以被有效序列化的值

    2.5 panic 的处理

    怎样施加应对 panic 的保护措施,从而避免程序崩溃?

    Go 语言的内建函数 recover 专用于恢复 panic,或者说平息运行时恐慌。recover 函数无需任何参数,并且会返回一个空接口类型的值

    三、recover

    3.1 具体示例

    
    package main
    
    import "fmt"
    
    func main() {
    
    	panic("test panic")
    
    	p := recover()
    
    	fmt.Printf("panic: %s 
    ", p)
    
    }
    

    这个 recover 函数调用并不会起到任何作用,甚至都没有机会执行

    panic 一旦发生,控制权就会讯速地沿着调用栈的反方向传播。所以,在 panic 函数调用之后的代码,根本就没有执行的机会

    那如果我把调用 recover 函数的代码提前呢?也就是说,先调用 recover 函数,再调用 panic 函数会怎么样呢?

    这显然也是不行的,因为,如果在我们调用 recover 函数时未发生 panic,那么该函数就不会做任何事情,并且只会返回一个 nil

    那么,到底什么才是正确的 recover 函数用法呢?这就不得不提到 defer 语句了

    四、defer

    4.1 基本解释

    defer 语句就是被用来延迟执行代码的

    延迟到什么时候呢?这要延迟到该语句所在的函数即将执行结束的那一刻,无论结束执行的原因是什么。

    一个 defer 语句总是由一个 defer 关键字和一个调用表达式组成

    4.2 defer 函数的限制

    有一些调用表达式是不能出现在这里的,包括

    (1) 针对 Go 语言内建函数的调用表达式

    (2) 针对 unsafe 包中的函数的调用表达式

    被调用的函数可以是有名称的,也可以是匿名的

    叫做 defer 函数或者延迟函数。注意,被延迟执行的是 defer 函数,而不是 defer 语句。

    4.3 具体示例

    
    package main
    
    import "fmt"
    
    func main() {
    
    	fmt.Println("--enter main func--")
    
    	defer func() {
    
    		fmt.Println("--enter defer func--")
    
    		p := recover()
    
    		if p != nil {
    
    			fmt.Printf("panic: %s 
    ", p)
    		}
    
    		fmt.Println("--defer func end--")
    
    	}()
    
    	panic("test panic")
    
    	fmt.Println("--main func end--")
    
    }
    
    // 运行结果
    ➜  others git:(master) ✗ go run main.go
    --enter main func--
    --enter defer func--
    panic: test panic
    --defer func end--
    
    

    4.4 多个 defer

    如果一个函数中有多条 defer 语句,那么那几个 defer 函数调用的执行顺序是怎样的?

    在 defer 语句每次执行的时候,Go 语言会把它携带的 defer 函数及其参数值另行存储到一个链表中

    这个链表与该 defer 语句所属的函数是对应的,并且,它是先进后出(FILO)的,相当于一个栈。

    在需要执行某个函数中的 defer 函数调用的时候,Go 语言会先拿到对应的链表,然后从该链表中一个一个地取出 defer 函数及其参数值,并逐个执行调用

    (1) 在同一个函数中,defer 函数调用的执行顺序与它们分别所属的 defer 语句的出现顺序(更严谨地说,是执行顺序)完全相反

    (2) 如果函数中有一条 for 语句,并且这条 for 语句中包含了一条 defer 语句, 那么,显然这条 defer 语句的执行次数,就取决于 for 语句的迭代次数

  • 相关阅读:
    python:返回函数,闭包
    对象的行为和数组
    类、对象和包
    Java语言中的程序流程控制
    初识Java,Java语言概述
    有限广播地址与直接广播地址
    H3C模拟器HCL注意事项
    HDLC协议
    NETBIOS的作用
    HP DL380G7 RAID配置
  • 原文地址:https://www.cnblogs.com/thewindyz/p/14366489.html
Copyright © 2011-2022 走看看