defer:
在看go标准库的时候,不时能看到函数中有一些defer语句,一开始挺疑惑,后来查了下,发现这是内置的关键字,用来确保某个函数调用 在当前函数执行结束时 执行:
func CopyFile(dst, src string) (written int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return
}
defer dstFile.Close()
return io.Copy(dstFile, srcFile)
}
上面是个常见的例子,执行完copyFile操作之后,打开的源文件和目标文件都能确保被释放。
如果不使用defer的话,代码可能是这样:
...
written, err = io.Copy(dst, src)
dstFile.Close()
srcFile.Close()
return
这种情况下,如果执行os.create()方法报错,scrFile还没来得及Close(),函数就因为err!=nil返回了。
defer也常用于释放数据库连接,或释放sync.mutex的互斥锁。
//释放互斥锁
mu.Lock()
defer mu.Unlock()
...
//关闭response body
resp, err := http.Get("http://example.com/")
if err != nil {
// handle error
}
defer resp.Body.Close()
...
一个函数内部可以defer多个待执行函数,这些待执行函数会以栈的方式保存在一个list中,后进先出地执行。
今天看到一篇官方post:Defer, Panic, and Recover
发现defer除了调用顺序外,还有一些需要注意的地方:
1.如果defer的待执行函数带参,比如(i int)。参数的值为执行到该defer时i的值:
func a() {
i := 0
i++
defer fmt.Println(i) //此时i值为1,随后该函数执行时i为1
defer fmt.Println("second")
defer fmt.Println("first")
i++
return
}
func main(){
a()
}
//打印出:
first
second
1
2.如果defer所在的函数声明了返回,defer的待执行函数可以读取并修改这个返回值:
func c() (i int) {
defer func() { i++ }()
return 1
}
如上,函数c声明了返回,其内defer的待执行函数读取并修改了i。c()返回的值为2。
panic:
go中有个内置的函数panic。panic用来中止当前的执行流。如果函数F在执行期间调用了panic函数,则函数F()立即中止,defer的待执行函数照常执行,然后F返回给调用F的那个函数。此时F也作为一个panic函数,中止当前函数,一直沿着调用栈直到这个goroutine的顶层,直到该goroutine返回。也就是说,这个程序崩了。
panic可以直接调用,也可以由运行时错误产生。
recover:
对于被panic的goroutine,go提供了一个recover内置函数,用来恢复被panic中止的goroutine。recover函数只对被panic中止的goroutine有用,正常情况下调用recover只会返回nil。
下面引用它的demo:
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
//函数f调用函数g,但是defer了一个func函数用于检测是否发现panic
//func函数始终执行recover(),确保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.")
}
//g函数内部调用panic
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)
}
recover可以防止程序意外崩坏,如果把f函数中的defer func去掉,会打印出:
panic: 4
panic PC=0x2a9cd8
[stack trace omitted]