一,go 语言 panic 报错捕获 

    使用 go 语言的同学在真实项目中应该经常出现空指针使用等 panic 报错,这类报错与 C++ 中的 try-catch 模块不同,go 语言会一直将当前 panic 一直从报错栈传至最外层的栈,所以很多 go 语言的架构都会在架构中 handler 的入口添加一串代码

1     defer func() {
2         if x := recover(); x != nil {
3             // TODO fix panic
4         }
5     }()

     这里讲几个关键字

     defer:注册一个回调函数,在当前栈退出时,按注册入栈的顺序,从最后注册的 defer 函数开始执行,函数内部发生 panic,属于可修复型崩溃,所以 go 语言会有序的退出栈,并执行 defer 函数

     recover:捕捉 panic 异常,并打断当前的 panic,进行处理修复,保证不会让单个 handler 影响到整个程序

     上述的异常捕获方法想必熟悉 go 语言的同学基本都能了解。但下面我们了解一些 go 语言中无法崩溃和修复的 throw 崩溃

 二,go 语言 throw 奔溃 

      其实 go 语言源码中一些地方有一些 throw 调用,这个函数会打印相应的 fatal msg,并退出整个程序,因为这类报错被 go 语言认为无法动态修复的崩溃。所以这类奔溃与 panic 不同,属于无法通过 defer 和 recover 捕获的崩溃(因为无法修复),简单举两个栗子

      lock:当使用一个初始化的锁,并未加锁就在代码中就解锁,就会发生 throw 崩溃

1 var lock sync.Mutex
2 lock.Unlock()  // fatal: sync: unlock of unlocked mutex
3
4
5 // from go 1.91
6 new := atomic.AddInt32(&m.state, -mutexLocked)
7 if (new+mutexLocked)&mutexLocked == 0 {
8     throw("sync: unlock of unlocked mutex") // post a throw
9 }

      map:熟悉 go 语言的开发者都知道,在 go 多携程架构使用便利的情况下,往往存在很多线程不安全的变量,map 就是其中最经典的栗子,当在并发下,在没有添加读写锁的情况下对 map 进行写、读写操作时,也会抛出 throw 崩溃

testmap := make([int],1)
for i := 0; i < 1000; i++ {
    go func() {
                for true{
                    testmap[1] = 10 // fatal: sync: curcurent map writes
                }
    }()
}

      需要注意的是,这类崩溃是直接 down 掉整个进程的,所以我们线上使用 go 语言进行应用开发时,一定要记得使用 supervisor 之类的进程管理工具,确保进行崩溃后先拉起来,再进行修复。否则会产生大面积机器完全宕机的情况。

      再需要注意的一点是,go 语言中一个进程往往有很多 goroutinue 在同时进行,如果发生 throw 奔溃时,整个进程都会被关掉,如果通过日志,会发现打印了无数堆栈信息的日志(所有 goroutinue 的日志),这时候千万不要在堆栈日志上下功夫了,因为打印出来的都是正常日志,只需要查看日志中的 fatal 关键字即可找出真正的问题所在。

01-01 01:14