【译】defer-panic-and-recover
Go 有通用的控制流程:if,for,switch,goto。它也有go语句用于让代码运行在单独的协程。这里我将讨论一些不常见的问题:defer,panic 和 recover。
defer语句将函数调用推送到列表。这个保存调用的列表在函数返回后执行。defer
通常用于简化执行各种清理操作。
例如,让我们看一个打开两个文件并将一个文件的内容复制到另一个文件的函数:
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
dst, err := os.Create(dstName)
if err != nil {
return
}
written, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}
这个能工作,但有一个漏洞。如果调用os.Create
失败,函数将在不关闭源文件的情况下返回。这可以轻松补救,在第二个return
语句之前调用src.Close
,但如果函数更复杂,则问题可能不那么容易被注意到和解决。通过引入defer
语句,我们可以确保文件始终关闭:
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
defer
语句允许我们在打开每个文件后立即考虑关闭它,从而保证无论函数中的return
语句数量是多少,这些文件都将被关闭。
defer
语句的行为是简单直接且可预测的。有三个简单的规则:
1、当defer
被声明时,其参数就会被计算。
在此示例中,当Println
调用被defer声明,将计算表达式“i”。defer
调用将在函数返回后打印“0”。
2、defer
的执行顺序为先进后出。
此函数打印“3210”:
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
3、defer
可以读取有名返回值。
在此示例中,defer函数在函数返回后递增返回值i
。因此,此函数返回2:
func c() (i int) {
defer func() { i++ }()
return 1
}
这对于修改函数的错误返回值很方便;我们很快就会看到一个这样的例子。
Panic
是一个内置功能,可以停止普通的控制流程并开始Panicing
。当函数F
调用panic
时,F
的执行会停止,F
中的任何defer
函数都正常执行,然后F
返回给其调用方。对于调用方,F
的表现就像是panic
。该过程继续在堆栈中向上移动,直到当前协程 中的所有函数都返回,此时程序崩溃。可以通过直接调用panic
来启动panic
。它们也可能是由运行时错误引起的,例如越界数组访问。
recover
是一个内置功能,可以重新获得对正在panic
的协程的控制。recover
仅在defer
函数中有用。在正常执行期间,recover
调用将返回nil
,并且没有其他效果。如果当前协程 出现panic
,则调用recover
将捕获panic
提供的值并恢复正常执行。
下面是一个示例程序,演示了panic
和defer
的机制:
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
函数g
获取int i
,如果i
大于 3,则会出现panic
,否则它将使用参数i+1
调用自己。函数f
defer
调用recover
并输出恢复值的函数(如果该值为非 nil
)。在继续阅读之前,请尝试想象此程序的输出可能是什么。
程序将输出:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
如果我们从f
中删除derfer
函数,则不会恢复panic
,并在到达协程调用堆栈顶部时终止程序。这个修改后的程序将输出:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
panic PC=0x2a9cd8
[stack trace omitted]
有关panic
和recover
的真实例子,请参阅Go
标准库中的json 包。它使用一组递归函数对接口进行编码。如果在遍历该值时发生错误,则会调用panic
将堆栈展开到最上层的函数调用,该调用将从panic
中恢复并返回适当的错误值(请参阅 encode.go 中 encodeState 类型的“error”和“marshal”方法)。
Go
库中的约定是,即使包在内部使用panic
,其对外的API
仍会展示显式错误返回值。
defer
的其他用法(除了前面给出的file.Close
例子)还包括释放互斥锁:
mu.Lock()
defer mu.Unlock()
打印页脚:
printHeader()
defer printFooter()
以及更多。
总之,defer
语句(有/没有panic
和recover
)为控制流提供了一种不同寻常且功能强大的机制。它可用于对其他编程语言中特殊用途结构实现的许多特性进行建模。试试吧。
原文 https://golang.google.cn/blog/defer-panic-and-recover
【译】defer-panic-and-recover的更多相关文章
- GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover
Simple error handling primitives: https://github.com/pkg/errors Defer, Panic, and Recover: ...
- 15 Defer, Panic, and Recover
Defer, Panic, and Recover 4 August 2010 Go has the usual mechanisms for control flow: if, for, switc ...
- Golang 入门系列(十四)defer, panic和recover用法
以前讲过golang 的基本语法.但是,只是讲了一些基础的语法,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863 ...
- 6.Go-错误,defer,panic和recover
6.1.错误 Go语言中使用builtin包下error接口作为错误类型 Go语言中错误都作为方法/函数的返回值 自定义错误类型 //Learn_Go/main.go package main imp ...
- go语言defer panic recover用法总结
defer defer是go提供的一种资源处理的方式.defer的用法遵循3个原则 在defer表达式被运算的同时,defer函数的参数也会被运算.如下defer的表达式println运算的同时,其入 ...
- panic和recover的使用规则
转自个人博客 chinazt.cc 在上一节中,我们介绍了defer的使用. 这一节中,我们温习一下panic和recover的使用规则. 在golang当中不存在tye ... catch 异常处理 ...
- Go语言异常处理defer\panic\recover
Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱.因为开发者很容易滥用异常, ...
- 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)
这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...
- go语言中使用defer、panic、recover处理异常
go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...
- Go基础系列:defer、panic和recover
defer关键字 defer关键字可以让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即便函数中途报错结束.即便已经panic().即便函数已经return了,也都会执行defer所推迟 ...
随机推荐
- 使用 RabbitMQ 有什么好处?
(1)服务间高度解耦 (2)异步通信性能高 (3)流量削峰
- Kafka创建Topic时如何将分区放置到不同的Broker中?
副本因子不能大于 Broker 的个数: 第一个分区(编号为0)的第一个副本放置位置是随机从 brokerList 选择的: 其他分区的第一个副本放置位置相对于第0个分区依次往后移.也就是如果我们有5 ...
- 如果一个表有一列定义为 TIMESTAMP,将发生什么?
每当行被更改时,时间戳字段将获取当前时间戳. 列设置为 AUTO INCREMENT 时,如果在表中达到最大值,会发生什么情况? 它会停止递增,任何进一步的插入都将产生错误,因为密钥已被使用. 怎样才 ...
- 如何在 spring 中启动注解装配?
默认情况下,Spring 容器中未打开注解装配.因此,要使用基于注解装配,我们 必须通过配置 <context:annotation-config/> 元素在 Spring 配置文件 中启 ...
- 什么是 Spring 的依赖注入?
依赖注入,是 IOC 的一个方面,是个通常的概念,它有多种解释.这概念是说你 不用创建对象,而只需要描述它如何被创建.你不在代码里直接组装你的组件和 服务,但是要在配置文件里描述哪些组件需要哪些服务, ...
- 数仓建模—OneID
今天是我在上海租房的小区被封的第三天,由于我的大意,没有屯吃的,外卖今天完全点不到了,中午的时候我找到了一包快过期的肉松饼,才补充了1000焦耳的能量.但是中午去做核酸的时候,我感觉走路有点不稳,我看 ...
- c语言 相关小知识
软件运行与内存关系(垃圾数据) 内存是在操作系统的统一管理下使用的! 1.软件在运行前需要向操作系统申请访问存储空间,在内存空闲空间足够时,操作系统将分配一段内存空间并将外存中软件拷贝一份存入该内存空 ...
- 通过HTML5的getUserMedia实现拍照功能
参考HTML5Rocks的这篇文章实现的一个简单的例子. 思路如下: 1. 把冰箱门打开 2. 把大象放进冰箱里 3. 把冰箱门关上 好了不开玩笑了,其实思路是: 1. 通过getUserMedia调 ...
- c++语法拾遗,一些细节与特性
写了2年多的C+STL的acmer,在学习<C++ primer>时总结的一些少见的语法特性与细节.总体还是和题目说的一样这是一篇 c++ 拾遗. 1 变量和基本类型 1.1 基本类型 1 ...
- Android bluetoothAdapter.startDiscovery()无法搜索设备问题解决办法
Android6.0以上要定位权限,要手动把手机软件的定位权限打开,又被坑了好长时间