Go语言通常有流程控制机制:if,for,switch,goto。还具有go语句在单独的协程中运行代码。讨论一些不太常见的用法:defer,panic以及recover。
defer语句会构建一个函数保存到一个栈中。在函数return后会调用栈中保存的函数。
return并非原子操作,分为赋值,返回值两部分。
return先执行,先将结果写入返回值中也就是赋值;然后defer执行;最后函数携带当前返回值解释函数。
defer
匿名返回值
return时候会创建临时变量保存值
package main
import "fmt"
func main() {
n := noNameReturn()
fmt.Println(n)
}
func noNameReturn() int {
i := 5
defer func() {
i++
fmt.Println("defer 1:", i)
}()
defer func() {
i++
fmt.Println("defer 2:", i)
}()
return i
}
打印:
defer 2: 6
defer 1: 7
5
函数返回值匿名,return会创建个变量比如是v,把i值赋值给这个变量v,并且v和i都是5。之后defer的操作只是影响到了i,没有影响到变量v,因此i加了两次变成了6和7。v没有变还是5,return返回的值就是变量v,返回就是5。
有名返回值
return不会创建额外的变量了,defer会改变这个返回值,操作的都是一个变量。
package main
import "fmt"
func main() {
n := nameReturn()
fmt.Println(n)
}
func nameReturn() (i int) {
i = 5
defer func() {
i++
fmt.Println("defer 1:", i)
}()
defer func() {
i++
fmt.Println("defer 2:", i)
}()
return i
}
打印
defer 2: 6
defer 1: 7
7
操作的都是有名的变量名i,改变i的值会影响函数返回值。
defer栈
Last In First Out 后进先出
package main
import "fmt"
func main() {
b()
}
func b() {
for i := 0; i < 4; i++ {
defer fmt.Println(i)
}
}
打印
3
2
1
0
panic
panic是内置函数,会停止正常流程。当函数F产生panic的时候,F的执行会被迫停止,defer会正常执行。F函数返回。对于函数F的调用者,也会产生panic(这个panic是F函数里面产生的)。这个过程在程序函数堆栈中执行,向上传递,直到所有的函数都返回,程序崩溃。
产生panic两种方式:主动调用panic;runtime error 比如数组越界。
recover
recover是内置函数可以恢复协程产生的panic。recover只是在defer函数中可用。正常情况下执行recover返回nil并且没有什么副作用。如果当前协程产生了panic,调用recover函数的调用者会截取到给panic的值并恢复正常执行。
demo演示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 > 1 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer func() {
fmt.Println("Defer in g", i)
}()
fmt.Println("Printing in g", i)
g(i + 1)
}
打印
Calling g.
Printing in g 0
Printing in g 1
Panicking!
Defer in g 1
Defer in g 0
Recovered in f 2
Returned normally from f.
当进入g函数并且i是2的时候,会产生panic,但是此时defer还没有走到,因此没有执行这个defer,因为没有加入到defer的栈里。只是返回上一层的函数执行defer。直到panic传递到函数f,被recover捕获到,没有走最后一行的fmt.Println("Returned normally from g.")。使得程序正常运行,没有挂掉。
调整下把g里面的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) {
defer func() {
fmt.Println("Defer in g", i)
}()
if i > 1 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
fmt.Println("Printing in g", i)
g(i + 1)
}
打印
Calling g.
Printing in g 0
Printing in g 1
Panicking!
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 2
Returned normally from f.
进入g并且i是2,走到panic那一行的时候,已经走过defer了,猜测已经入栈,因此下面panic产生的时候正常执行defer,会打印出Defer in g 2。之后同上面。
如果删除掉f函数的defer,没有了recover。那么产生的panic会一直到报到栈顶,直到程序挂了,崩溃。
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
defer func() {
fmt.Println("Defer in g", i)
}()
if i > 1 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
fmt.Println("Printing in g", i)
g(i + 1)
}
打印
Calling g.
Printing in g 0
Printing in g 1
Panicking!
Defer in g 2
Defer in g 1
Defer in g 0
panic: 2
xxx/demo.go:6 +0x19
goroutine 1 [running]:
main.g(0x2)
Process finished with the exit code 2in/go-learn/demo.go:22 +0x199
main.g(0x1)
xxx/demo.go:25 +0xdf
main.g(0x0)
xxx/demo.go:25 +0xdf
main.f()
xxx/demo.go:12 +0x5d
main.main()
可以学习一下go的json库源码处理recover,当是jsonError的时候recover程序并返回异常信息。如果不是jsonError异常认为是程序崩溃了,此处不做recover恢复处理。
其他defer使用场景
有关闭文件
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
锁
mu.Lock()
defer mu.Unlock()
打印页脚
printHeader()
defer printFooter()