1. Go在什么情况下会Panic?
常见的有10种情况:
- 数组/切片索引越界;
- 引用空指针;
- 除以零;
- 向已经关闭的通道发消息;
- 关闭一个已关闭的通道;
- 关闭未初始化的通道;
- 写入未初始化的
map; - 并发写
map; - 跨协程的
panic处理; sync计数为负值 :wg.Add(-1)。
2. 协程panic会不会导致主进程panic?
会,协程panic会导致主协程panic,主协程结束(main函数返回)时,所有在main函数中启动的goroutine会一同结束,因为main函数结束相当于程序结束。
例1:协程panic会导致主进程异常退出
// 主协程还没结束,触发了panic
func main() {
go func() {
panic("err: xxx")
}()
log.Println("Done")
}
例2:主进程先退出,协程后painc不会影响主进程
// 主协程在协程panic之前结束了,不会触发panic
func main() {
go func() {
time.Sleep(1 * time.Second) // 等待主协程先结束
panic("err: xxx")
}()
log.Println("Done")
}
3. 如何防止panic?
可以通过defer + recover的方式捕获panic,这样即使触发了panic,程序也不会异常退出:
package main
import (
"log"
)
// panic部分
func main() {
go func() {
defer func() {
if e := recover(); e != nil {
log.Printf("recover: %v", e)
}
}()
panic("err: xxx")
}()
log.Println("Done")
}
4. map的并发写panic能够捕获吗?
Go中有些panic是无法被捕获的,只要触发就会导致整个程序退出。在Go 1.16之后,map的并发读写被设计为无法捕获,发现并发读写后会调用throw(),throw()又调用fatalthrow(),而fatalthrow()方法会导致不可恢复的错误直接退出程序。
package main
import "log"
func main() {
var a = map[int]int{}
defer func() {
if err := recover(); err != nil {
log.Printf("main recover: %v", err)
}
}()
go func() {
defer func() {
if err := recover(); err != nil {
log.Printf("goroutine recover: %v", err)
}
}()
for {
a[1] = 1 // 协程中写map
}
}()
for {
a[1] = 1 // 主协程中也在写map
}
}