go 的 http 服务会一直在监听某个端口,来监听客户端发过来的请求,并且要为每个请求开辟一个单独的协程。这样看起来每个协程都是独立的,不不影响。
但是,如果在某个子协程里,发生了 panic 异常,会导致服务端进程退出,从而发生严重的线上故障。
例如下面服务,将引发 panic,当然引发 panic 的还有除以 0 等操作:
package main
import (
"time"
)
func main() {
go func() {
var info map[string]string
// 对未初始化的map进行赋值,panic
info["a"] = ""
}()
for {
time.Sleep(time.Second)
}
}
当引发 panic 时候,panic 会引发三件事情:
- 逆序执行当前 goroutine 的 defer 链(recover 从这里介入)。
- 打印错误信息和调用堆栈
- 调用
exit
结束整个进程
所以,我们可以在 defer 里使用 recover 函数,来阻断后面两步的执行,例如:
package main
import (
"fmt"
"time"
)
func main() {
go func() {
defer func() {
if err := recover(); err != nil {
// 输出:assignment to entry in nil map
fmt.Println(err)
}
}()
var info map[string]string
// 对未初始化的map进行赋值,panic
info["a"] = ""
}()
for {
time.Sleep(time.Second)
}
}
触发 panic 的场景总结
数组越界的时候
s := []int{1, 2, 3}
for i := 0; i <= 4; i++ {
fmt.Println(s[i])
}
在实际开发中,也可以主动调用 panic 函数达到同样效果。
func panicFunc() {
panic(errors.New("this is test for panic"))
}
对未初始化的map进行赋值的时候
var info map[string]string
// 对未初始化的map进行赋值,panic
info["a"] = ""
空指针的时候