Go的Panic相关问题

847 阅读2分钟

1. Go在什么情况下会Panic?

常见的有10种情况:

  1. 数组/切片索引越界;
  2. 引用空指针;
  3. 除以零;
  4. 向已经关闭的通道发消息;
  5. 关闭一个已关闭的通道;
  6. 关闭未初始化的通道;
  7. 写入未初始化的map
  8. 并发写map
  9. 跨协程的panic处理;
  10. 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")
}

1701690065614.png

例2:主进程先退出,协程后painc不会影响主进程

// 主协程在协程panic之前结束了,不会触发panic
func main() {
	go func() {
		time.Sleep(1 * time.Second)	// 等待主协程先结束
		panic("err: xxx")
	}()

	log.Println("Done")
}

1701691681792.png

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")
}

1701689649153.png

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
	}
}

image.png