go defer,panic,recover详解 go 的异常处理

2023-05-16

转载 https://www.jianshu.com/p/63e3d57f285f

golang中defer,panic,recover是很常用的三个特性,三者一起使用可以充当其他语言中try…catch…的角色,而defer本身又像其他语言的析构函数

defer

defer后边会接一个函数,但该函数不会立刻被执行,而是等到包含它的程序返回时(包含它的函数执行了return语句、运行到函数结尾自动返回、对应的goroutine panic)defer函数才会被执行。通常用于资源释放、打印日志、异常捕获等

func main() {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    /**
     * 这里defer要写在err判断的后边而不是os.Open后边
     * 如果资源没有获取成功,就没有必要对资源执行释放操作
     * 如果err不为nil而执行资源执行释放操作,有可能导致panic
     */
    defer f.Close()
}

如果有多个defer函数,调用顺序类似于栈,越后面的defer函数越先被执行(后进先出)

func main() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    defer fmt.Println(4)
}

结果:

4
3
2
1

如果包含defer函数的外层函数有返回值,而defer函数中可能会修改该返回值,最终导致外层函数实际的返回值可能与你想象的不一致,这里很容易踩坑,来几个🌰

例1

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

例2

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

例3

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

请先不要向下看,在心里跑一遍上边三个例子的结果,然后去验证
可能你会认为:例1的结果是0,例2的结果是10,例3的结果是6,那么很遗憾的告诉你,这三个结果都错了
为什么呢,最重要的一点就是要明白,return xxx这一条语句并不是一条原子指令
含有defer函数的外层函数,返回的过程是这样的:先给返回值赋值,然后调用defer函数,最后才是返回到更上一级调用函数中,可以用一个简单的转换规则将return xxx改写成

返回值 = xxx
调用defer函数(这里可能会有修改返回值的操作)
return 返回值

例1可以改写成这样

func f() (result int) {
    result = 0
    //在return之前,执行defer函数
    func() {
        result++
    }()
    return
}

所以例1的返回值是1

例2可以改写成这样

func f() (r int) {
    t := 5
    //赋值
    r = t
    //在return之前,执行defer函数,defer函数没有对返回值r进行修改,只是修改了变量t
    func() {
        t = t + 5
    }
    return
}

所以例2的结果是5

例3可以改写成这样

func f() (r int) {
    //给返回值赋值
    r = 1
    /**
     * 这里修改的r是函数形参的值,是外部传进来的
     * func(r int){}里边r的作用域只该func内,修改该值不会改变func外的r值
     */
    func(r int) {
        r = r + 5
    }(r)
    return
}

所以例3的结果是1

defer函数的参数值,是在申明defer时确定下来的

在defer函数申明时,对外部变量的引用是有两种方式:作为函数参数和作为闭包引用
作为函数参数,在defer申明时就把值传递给defer,并将值缓存起来,调用defer的时候使用缓存的值进行计算(如上边的例3)
而作为闭包引用,在defer函数执行时根据整个上下文确定当前的值
看个🌰

func main() {
    i := 0
    defer fmt.Println("a:", i)
    //闭包调用,将外部i传到闭包中进行计算,不会改变i的值,如上边的例3
    defer func(i int) {
        fmt.Println("b:", i)
    }(i)
    //闭包调用,捕获同作用域下的i进行计算
    defer func() {
        fmt.Println("c:", i)
    }()
    i++
}

结果:

c: 1
b: 0
a: 0

by smoke_zl

panic和recover

func panic(v interface{})

中英文说明
The panic built-in function stops normal execution of the current goroutine.When a function F calls panic, normal execution of F stops immediately.Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller. To the caller G, the invocation of F then behaves like a call to panic,terminating G's execution and running any deferred functions.This continues until all functions in the executing goroutine have stopped,in reverse order. At that point, the program is terminated and the error condition is reported,including the value of the argument to panic. This termination sequence is called panicking and can be controlled by the built-in function recover.
panic内置函数停止当前goroutine的正常执行,当函数F调用panic时,函数F的正常执行被立即停止,然后运行所有在F函数中的defer函数,然后F返回到调用他的函数对于调用者G,F函数的行为就像panic一样,终止G的执行并运行G中所defer函数,此过程会一直继续执行到goroutine所有的函数。panic可以通过内置的recover来捕获。

func recover() interface{}

中英文说明
The recover built-in function allows a program to manage behavior of a panicking goroutine. Executing a call to recover inside a deferred function (but not any function called by it) stops the panicking sequence by restoring normal execution and retrieves the error value passed to the call of panic. If recover is called outside the deferred function it will not stop a panicking sequence. In this case, or when the goroutine is not panicking, or if the argument supplied to panic was nil, recover returns nil. Thus the return value from recover reports whether the goroutine is panicking.
recover内置函数用来管理含有panic行为的goroutine,recover运行在defer函数中,获取panic抛出的错误值,并将程序恢复成正常执行的状态。如果在defer函数之外调用recover,那么recover不会停止并且捕获panic错误如果goroutine中没有panic或者捕获的panic的值为nil,recover的返回值也是nil。由此可见,recover的返回值表示当前goroutine是否有panic行为

这里有几个需要注意的问题,通过🌰表现

1、defer 表达式的函数如果定义在 panic 后面,该函数在 panic 后就无法被执行到

在defer前panic

func main() {
    panic("a")
    defer func() {
        fmt.Println("b")
    }()
}

结果,b没有被打印出来:

panic: a

goroutine 1 [running]:
main.main()
    /xxxxx/src/xxx.go:50 +0x39
exit status 2

而在defer后panic

func main() {
    defer func() {
        fmt.Println("b")
    }()
    panic("a")
}

结果,b被正常打印:

b
panic: a

goroutine 1 [running]:
main.main()
    /xxxxx/src/xxx.go:50 +0x39
exit status 2

2、F中出现panic时,F函数会立刻终止,不会执行F函数内panic后面的内容,但不会立刻return,而是调用F的defer,如果F的defer中有recover捕获,则F在执行完defer后正常返回,调用函数F的函数G继续正常执行

func G() {
    defer func() {
        fmt.Println("c")
    }()
    F()
    fmt.Println("继续执行")
}

func F() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("捕获异常:", err)
        }
        fmt.Println("b")
    }()
    panic("a")
}

结果:

捕获异常: a
b
继续执行
c

3、如果F的defer中无recover捕获,则将panic抛到G中,G函数会立刻终止,不会执行G函数内后面的内容,但不会立刻return,而调用G的defer...以此类推

func G() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("捕获异常:", err)
        }
        fmt.Println("c")
    }()
    F()
    fmt.Println("继续执行")
}

func F() {
    defer func() {
        fmt.Println("b")
    }()
    panic("a")
}

结果:

b
捕获异常: a
c

4、如果一直没有recover,抛出的panic到当前goroutine最上层函数时,程序直接异常终止

func G() {
    defer func() {
        fmt.Println("c")
    }()
    F()
    fmt.Println("继续执行")
}

func F() {
    defer func() {
        fmt.Println("b")
    }()
    panic("a")
}

结果:

b
c
panic: a

goroutine 1 [running]:
main.F()
    /xxxxx/src/xxx.go:61 +0x55
main.G()
    /xxxxx/src/xxx.go:53 +0x42
exit status 2

5、recover都是在当前的goroutine里进行捕获的,这就是说,对于创建goroutine的外层函数,如果goroutine内部发生panic并且内部没有用recover,外层函数是无法用recover来捕获的,这样会造成程序崩溃

func G() {
    defer func() {
        //goroutine外进行recover
        if err := recover(); err != nil {
            fmt.Println("捕获异常:", err)
        }
        fmt.Println("c")
    }()
    //创建goroutine调用F函数
    go F()
    time.Sleep(time.Second)
}

func F() {
    defer func() {
        fmt.Println("b")
    }()
    //goroutine内部抛出panic
    panic("a")
}

结果:

b
panic: a

goroutine 5 [running]:
main.F()
    /xxxxx/src/xxx.go:67 +0x55
created by main.main
    /xxxxx/src/xxx.go:58 +0x51
exit status 2

6、recover返回的是interface{}类型而不是go中的 error 类型,如果外层函数需要调用err.Error(),会编译错误,也可能会在执行时panic

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("捕获异常:", err.Error())
        }
    }()
    panic("a")
}

编译错误,结果:

err.Error undefined (type interface {} is interface with no methods)

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("捕获异常:", fmt.Errorf("%v", err).Error())
        }
    }()
    panic("a")
}

结果:

捕获异常: a

参考:
https: //golang.org/pkg/builtin/#recover
https: //www.w3cschool.cn/go_internals/go_internals-drq7282o.html


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

go defer,panic,recover详解 go 的异常处理 的相关文章

  • Linux远程图形化界面出错:MoTTY X11 proxy: Unsupported authorisation protocol

    远程使用图形化界面时出错 xff1a MoTTY X11 proxy Unsupported authorisation protocol 解决 xff1a cp root Xauthority home xxx Xauthority xx
  • (超详细)零基础如何学习操作系统---操作系统书籍推荐

    直接先给出路线书籍 编码 隐匿在计算机软硬件背后的语言 汇编语言 x86从实模式到保护模式 操作系统导论 操作系统真象还原 查漏补缺 1 编码 隐匿在计算机软硬件背后的语言 对于完全不懂计算机的朋友 xff0c 这本书可以让你对计算机有一个
  • 【LeetCode】《LeetCode 101》第七章:动态规划

    文章目录 7 1 算法解释7 2 基本动态规划 xff1a 一维70 爬楼梯 xff08 简单 xff09 198 打家劫舍 xff08 中等 xff09 413 等差数列划分 xff08 中等 xff09 7 3 基本动态规划 xff1a
  • CentOS使用Nginx反向代理Tomcat部署Java Web项目

    CentOS使用Nginx反向代理Tomcat部署Java Web项目 今天我舍友问我 xff1a CSDN是什么意思 xff1f 床上等你吗 文章目录 CentOS使用Nginx反向代理Tomcat部署Java Web项目一 安装Tomc
  • yield函数在Python中的使用方式探究

    Python之yield学习 Python之yield1 问题描述2 yield版解决方案3 yield与generator4 总结 Python之yield 1 问题描述 在之前的学习中 xff0c 对于返回需要进行迭代序列的函数 xff
  • eslint报错解决

    Unexpected control character s in regular expression x00 no control regex eslint disable next line no control regex 用这个
  • Mac中brew的安装和常用命令

    brew是MAC中的一个包管理工具 类似于Linux中的那些自动下载yum包 通过brew可以自动下载安装很多软件包在 https brew sh 中有它的具体内容 在页面里可以找到所有安装步骤和可安装的包 安装brew 终端输入以下命令行
  • 华为2288v5服务器安装Centos7系统

    1 安装准备 1 1 硬件 以虚拟光驱 虚拟软驱为例介绍操作系统的安装过程 xff0c 如果使用物理光驱 物理软驱则以实际的光盘 软盘代替 1 2 软件 xff08 1 xff09 准备好操作系统的 ISO 镜像包 xff08 2 xff0
  • 设置docker自启动

    1 设置docker自启动 sudo systemctl enable docker 2 查看docker中的镜像 sudo docker images 3 配置默认的镜像加速 sudo mkdir p etc docker sudo te
  • ESLint 报错:error Unreachable code no-unreachable

    大多出错的原因都与 return 有关 xff0c 可能是带有返回值的方法之中调用另一个方法的时候 把方法写在了 return 后面 代码的意思就是没有执行到这个方法 xff1b 还有可能就是在遍历判断中使用了 return xff0c 符
  • 程序员:每一份不忘初心的情怀, 都是对技术的追求

    1 真正的情怀 xff0c 是从不矫情 这几年 情怀 大约快成了贬义 创业讲情怀 xff0c 产品讲情怀 xff0c 好像没点情怀都不好意思出门见人 我们曾经充满热情 xff0c 是一位开源软件倡导者 xff0c 我们崇尚全栈工程师才有未来
  • ubuntu 如何用root身份进行登录

    公司有个小项目 需要用python调用 sh脚本来执行一些东西 执行脚本的时候需要输入密码 类似 sudo S paaswd 43 脚本 但是给客户部署的话 再让客户客户 保存密码到配置文件 就显得麻烦 就想到用root方式去登陆系统 结果
  • 创建Maven项目时候弹出setup maven plugin connection解决方案

    今天创建一个maven项目的时候弹出了setup maven plugin connection xff0c 然后有两个选择 xff0c 我们直接选择auto select继续下一步 xff0c 然后等待一下 xff0c 把此页面关掉 然后
  • 基于BERT的知识库问答系统(KBQA)

    基于BERT的知识库问答系统 xff08 KBQA xff09 简介 知识库问答系统主要要做的两点分别是识别问题中的实体和提取问题中我们需要从知识库中查找的属性 xff0c 针对这两个方面我采用了NLP预训练模型BERT分别训练了基于BER
  • libc6-dev : 依赖: libc6 (= 2.23-0ubuntu3) 但是 2.23-0ubuntu10 正要被安装

    无法安装libc6 dev 如果出现下面错误 xff1a 在装libc6 dev时下列的软件包有不能满足的依赖关系 xff1a libc6 dev 依赖 libc6 61 2 23 0ubuntu3 但是 2 23 0ubuntu10 正要
  • VScode使用sftp远程修改文件

    一 下载插件 二 创建任意一个文件夹 sftp插件是在文件夹下生效的 因为你要在文件夹下进行sftp的配置 以及打开远程文件 三 配置远程连接配置 快捷键 Ctrl 43 Shift 43 P 打开config span class tok
  • iOS 上接入Lottie动画 接入Json动画

    iOS设备上的动画借助官方SDK xff1a CoreAnimation那一套已经比较强大了 xff0c 基本上的动效都能实现 xff0c 就是1 开发这边比较累 xff1b 2 安卓那边也比较累况且 俩平台最后展示的可能有些细节还不一样
  • 安装pysyft联邦框架和pysyft代码案例

    pysyft联邦框架 1 新建虚拟环境2 安装pysyft3 安装jaxlib4 安装完成5 安装结果测试6 注意事项7 pysyft框架联邦案例8 一些优秀教程9 报错解决办法 1 新建虚拟环境 由于pysyft和pytorch之间有版本
  • Ps1终端提示符的参数设置

    1 了解PS1 PS1 61 u 64 h W PS1的常用参数以及含义 d xff1a 代表日期 xff0c 格式为weekday month date xff0c 例如 xff1a Mon Aug 1 H xff1a 完整的主机名称 h
  • Python3 实现简单的生命游戏

    Python3 实现简单的生命游戏 生命游戏是英国数学家约翰 何顿 康威在1970年发明的细胞自动机 生命游戏原理 细胞自动机 xff08 又称元胞自动机 xff09 xff0c 名字虽然很深奥 xff0c 但是它的行为却是非常美妙的 所有

随机推荐

  • 图论

    图是数据结构和算法学中最强大的框架之一 xff08 或许没有之一 xff09 图 xff08 graph xff09 并不是指图形图像 xff08 image xff09 或地图 xff08 map xff09 通常来说 xff0c 我们会
  • 【Python】【Pygame】游戏:抓球

    来源 xff1a Python编程 从入门到实践 13 5 14 1 14 2 我感觉这个题目继续学习还可以优化 xff0c 是一个不错的小游戏 等我学了记分再来继续改进 抓球 xff08 13 5 xff09 碗在底部左右移动 xff0c
  • Linux MariaDB 主从复制

    安装MariaDB 安装MariaDB教程 主从复制 搭建主从复制数据库不能先建数据库 xff0c 有数据库 xff0c 需要先删除 xff01 xff01 xff01 Master主数据库 主从复制详细参数列表 主服务器MariaDB的
  • Linux MariaDB使用OpenSSL安装SSL证书

    进入到证书存放目录 xff0c 批量删除 pem证书 警告 xff1a 确保已经进入到证书存放目录 find span class token punctuation span span class token operator span
  • Windows Server 2016创建用户、授权、其他用户远程登录、禁止/恢复administrator远程登录

    创建用户和授权管理员 win 43 s搜索控制面板 用户账户 更改账户类型 添加用户账户 完成 更改账户类型 管理员 更改账户类型 允许其他用户远程登录 win 43 s搜索高级系统设置 远程 远程桌面 允许远程连接到此计算机和勾选仅允许运
  • 使用kotlin扩展插件/依赖项简化代码(在最新版本4.0以后,此插件已被弃用,故请选择性学习,以了解为主。)

    1 添加取代findViewById XXX 的插件 xff1b id 39 kotlin android extensions 39 取代findViewById XXX 可以在kotlin代码中直接使用View上的控件id 2 扩展插件
  • 华为Fusion Compute通过存储LUN快照恢复数据

    上一篇博客里边写到了VMware虚拟化通过存储LUN快照恢复数据 xff0c 在里边提到华为虚拟化和其他KVM有区别 xff0c 这是真的血泪史呀 事情是这样的 xff0c 一个客户用的华为云桌面 xff0c 就是普通架构 xff0c 传统
  • 【转】ubuntu linux取消软件密码环和取消输入开机密码

    所用版本 xff1a ubuntu18 xff08 亲测16也成功了 xff09 取消软件密码环 win键打开菜单 gt 搜索pass word gt login选项 gt 右键删除某个密码环 转自 https www cnblogs co
  • 2_3-numpy-cnn-mnist手写数字识别

    numpy实现神经网络系列 工程地址 xff1a https github com yizt numpy neuron network 基础知识 0 1 全连接层 损失函数的反向传播 0 2 1 卷积层的反向传播 单通道 无padding
  • python获取threading线程返回结果

    python获取threading线程返回结果 span class token keyword class span span class token class name MyThread span span class token p
  • ADX的计算方式

    平均趋向指数是衡量趋势的技术工具 xff0c 简称ADX average directional indicator xff0c 它是由韦尔斯 怀尔德在1978年提出 xff0c 与其他技术分析工具不同的是 xff0c ADX并不能判断多空
  • uni app项目中实现vue和html通信

    vue实现 web view 是一个 web 浏览器组件 xff0c 可以用来承载网页的容器 xff0c 会自动铺满整个页面 个人业务 xff08 需要实现一个本地的html文件的内嵌 xff09 xff0c 由于web view是自动铺满
  • uni app实现中英文语言切换

    因为业务需求很多app都是可以多种语言进行切换的 xff0c 以此来方便用户的使用 做语言的切换一定要开发开始的时候就规划好 xff0c 不然确实太麻烦了 xff0c 我是后期开发的语言切换 xff0c 好多个页面都需要进行修改 main
  • uniapp一套代码开发app和微信小程序

    为什么选择uniapp开发 xff1f 为什么选择uniapp进行开发 xff1f 1 uniapp对于独自开发是相当友好的 xff0c 一套代码可以兼容app xff08 安卓 xff0c ios xff09 小程序 xff0c h5等
  • python + selenium的使用 - mac可能出现的问题

    注意服务器部署selenium时 xff0c 代码中一定要添加关闭浏览器驱动的语句 xff08 代码执行结束后不会自动关闭浏览器驱动 xff09 xff0c 否则会导致服务器磁盘被占满 1 安装selenium pip span class
  • 使用Truffle 和 Ganache编译、发布智能合约

    文章目录 Truffle开发框架步骤1 安装Truffle2 查看安装是否成功3 创建没有合约的空工程 xff0c 可以使用 truffle init4 编译Truffle项目的合约truffle compile编译问题 5 构建文件Art
  • 基于Ganache和MetaMask搭建以太坊私有网络

    使用Truffle 和 Ganache 编译 发布智能合约 请参考作者博客https blog csdn net weixin 43958804 article details 109576016 操作步骤 下载ganache https
  • python面试题--统计文件中字母出现的次数

    统计字母出现的次数 fp span class token operator 61 span span class token builtin open span span class token punctuation span span
  • 迁移EFI分区至固态硬盘

    接上回 加装SSD后的第二天 xff0c 我发现原来20s开机的电脑居然变成了30s开机 于是我就开始思考什么问题导致了这个情况 xff0c 根据启动顺序问题 xff0c 我发现可能是安装系统时 xff0c 我没拔下HDD的SATA0接口的
  • go defer,panic,recover详解 go 的异常处理

    转载 https www jianshu com p 63e3d57f285f golang中defer panic recover是很常用的三个特性 xff0c 三者一起使用可以充当其他语言中try catch 的角色 xff0c 而de