致命的应用程序退出|Go 中有哪些难以恢复的致命场景?
大家好,我是烤鱼。
在车祸现场,紧急恢复后,他正在检查代码一段时间。回头一看,这个错误提示明显是致命错误,还是定位比较好。
但此时,他实际上是在检查恐慌——如果他在那里错过了,我说这是一个很大的轰动......
明天烤鱼会和大家分享错误的类型以及触发的场景。
错误类型错误
第一个是 Go 中最标准的错误错误,它的实体是 {}。
如下:
- type error interface {
- Error() string
- }
在日常项目中,我们只需要创建任意结构体并实现Error方法致命的应用程序退出,就可以认为是error错误类型。
如下:
- type errorString struct {
- s string
- }
- func (e *errorString) Error() string {
- return e.s
- }
对外调用标准库API,通常如下:
- f, err := os.Open("filename.ext")
- if err != nil {
- log.Fatal(err)
- }
- // do something with the open *File f
我们会同意最后一个参数是错误类型,通常在第二个参数中常见,可以有一个习惯。
恐慌
第二个是Go中的异常处理panic,会形成异常错误。结合panic+,可以反转程序的运行状态。
如下:
- package main
- import "os"
- func main() {
- panic("a problem")
- _, err := os.Create("/tmp/file")
- if err != nil {
- panic(err)
- }
- }
输出结果:
- $ go run panic.go
- panic: a problem
- goroutine 1 [running]:
- main.main()
- /.../panic.go:12 +0x47
- ...
- exit status 2
如果不用作捕获,程序将被中断。所以经常被误认为是程序中断,100%是由panic引起的。
这是个误会。
投掷
Go初学者经常踩到但不知道的第三种错误是致命错误抛出。
这种错误类型不能在用户端主动调用。它是由Go本身底层调用的,比如你常见的map并发读写,就是由this触发的。
源码如下:
- func throw(s string) {
- systemstack(func() {
- print("fatal error: ", s, "n")
- })
- gp := getg()
- if gp.m.throwing == 0 {
- gp.m.throwing = 1
- }
- fatalthrow()
- *(*int)(nil) = 0 // not reached
- }
根据上述过程,将获得G的当前实例,并将其M的状态设置为1。
设置状态后,会调用该方法进行真正的crash相关操作:
- func fatalthrow() {
- pc := getcallerpc()
- sp := getcallersp()
- gp := getg()
- systemstack(func() {
- startpanic_m()
- if dopanic_m(gp, pc, sp) {
- crash()
- }
- exit(2)
- })
- *(*int)(nil) = 0 // not reached
- }
主要逻辑是发送信号量,最后调用exit方法退出,所以你会发现这是一个不可阻挡的“致命”错误。

致命一幕
因此,作为一个“成熟”的围棋工程师,我不仅要保证自己程序的健壮性,还收集了网上一些致命的错误场景,分享给大家。
让我们一起学习,避免这种致命的场景,争取在年底拿到A,不要背着P0车祸。
并发读写map
- func foo() {
- m := map[string]int{}
- go func() {
- for {
- m["煎鱼1"] = 1
- }
- }()
- for {
- _ = m["煎鱼2"]
- }
- }
输出结果:
- fatal error: concurrent map read and map write
- goroutine 1 [running]:
- runtime.throw(0x1078103, 0x21)
- ...
堆栈内存不足
- func foo() {
- var f func(a [1000]int64)
- f = func(a [1000]int64) {
- f(a)
- }
- f([1000]int64{})
- }
输出结果:
- runtime: goroutine stack exceeds 1000000000-byte limit
- runtime: sp=0xc0200e1bf0 stack=[0xc0200e0000, 0xc0400e0000]
- fatal error: stack overflow
- runtime stack:
- runtime.throw(0x1074ba3, 0xe)
- /usr/local/Cellar/go/1.16.6/libexec/src/runtime/panic.go:1117 +0x72
- runtime.newstack()
- ...
使用 nil 函数作为启动
- func foo() {
- var f func()
- go f()
- }
输出结果:
- fatal error: go of nil func value
- goroutine 1 [running]:
- main.foo()
- ...
死锁
- func foo() {
- select {}
- }
输出结果:
- fatal error: all goroutines are asleep - deadlock!
- goroutine 1 [select (no cases)]:
- main.foo()
- ...
线程限制已用完
如果你的被 IO 操作阻塞,可能会启动一个新线程来执行你的另一个。
Go 对最大线程数有一个默认限制,如果达到此限制,您的应用程序将崩溃。
会出现以下输出:
- fatal error: thread exhaustion
- ...
调用.可以减少线程数,但也要检查程序是否有问题。
超过可用的视频内存
如果执行操作,如:下载大文件等,导致应用占用显存过多,程序掉线,导致OOM。
会出现以下输出:
- fatal error: runtime: out of memory
- ...
建议删除一些程序或购买新的笔记本电脑。
总结
在明天的文章中,我们将介绍 Go 中的三种类型的错误。其中,介绍了致命错误致命的应用程序退出,这种错误最为罕见,但一旦遇到很容易倾覆,并给出了一些经典案例。
我希望你以后可以避免它。你有遇到过这些场景吗?