理解 Go 语言 panic 机制:错误类型、崩溃场景及修复

前言

我们在用go的时候,每次遇到程序退出,go会抛出一个panic的提示

Golang 复制代码
panic: runtime error: index out of range [5] with length 3 
goroutine 1 [running]: 
main.main() 
    /path/to/your/code/main.go:7 +0x45 
exit status 2

这就表示程序遇到了无法处理的错误,可能会导致崩溃。我们今天看下,panic机制的底层原理、场景,探讨下是否可以完全避免panic。

panic是什么?

golang的panic,是一种报错机制。 即:在golang在遇到无法处理的错误时,会使程序进入恐慌状态,提前终止当前代码的运行。一旦触发,函数会逐层回溯调用栈,执行每一层的defer,直到程序崩溃或者recover捕获panic。

不是所有的错误都能panic

当程序底层主动调用了panic()时,会将panic逐级回调。但还有的错误类型 是不能被panic的,比如并发读写map。这种场景下,可能会导致程序不一致或者不可恢复状态,让程序继续往下走,可能会导致DB或者缓存等等写脏等重大问题,所以还不如让程序崩溃。

线上服务常见的崩溃场景-不能从父gorutine恢复子gorutine

在起子协程时,因为父子协程不共享defer链,defer链是挂在GMP的G上的。当go一个新的gorutine,defer链并不会被迁移到子gorutine的G`上,这就导致子gorutine也需要重新去recover一遍,这非常容易错漏。

解决

有两种方式去共享gorutine之间的数据:

  1. 是共享内存加锁的方式
  2. 通过channel的方式,让gorutine之间完成通信

channel通信的代码

Golang 复制代码
package main

import (
    "fmt"
    "time"
)

// 全局通知通道,用于传递 panic 信息
var notifier chan interface{}

// 启动全局 panic 捕获机制
func startGlobalPanicCapturing() {
    // 创建一个无缓冲的通道
    notifier = make(chan interface{})
    // 启动一个 goroutine 来监听通道
    go func() {
        for {
            select {
            case r := <-notifier:
                // 打印捕获到的 panic 信息
                fmt.Printf("捕获到 panic: %v\n", r)
            }
        }
    }()
}

// Go 函数用于安全地启动一个 goroutine
func Go(f func()) {
    go func() {
        // 使用 defer 和 recover 来捕获 panic
        defer func() {
            if r := recover(); r != nil {
                // 将 panic 信息发送到通知通道
                notifier <- r
            }
        }()
        // 执行传入的函数
        f()
    }()
}

func main() {
    // 启动全局 panic 捕获机制
    startGlobalPanicCapturing()
    // 启动一个 goroutine,模拟数组越界访问引发 panic
    Go(func() {
        a := make([]int, 1)
        println(a[1])
    })
    // 等待一段时间,确保 panic 信息被捕获和处理
    time.Sleep(time.Second)
}

重点总结

  1. panic 是什么:说明 Go 语言中的 panic 是一种报错机制,当遇到无法处理的错误时,会使程序进入恐慌状态,提前终止当前代码运行,触发后函数逐层回溯调用栈,执行 defer,直至程序崩溃或被 recover 捕获。
  2. 不是所有的错误都能 panic:提到有些错误类型不能被 panic,如并发读写 map,这种情况可能导致程序出现不一致或不可恢复状态,甚至引发数据库或缓存写脏等重大问题,此时让程序崩溃或许更好。
  3. 线上服务常见的崩溃场景 - 不能从父 gorutine 恢复子 gorutine:解释了起子协程时,由于父子协程不共享 defer 链(defer 链挂在 GMP 的 G 上),新的 gorutine 中 defer 链不会迁移,导致子 gorutine 需重新 recover,容易出现错漏。
相关推荐
Bella的成长园地7 分钟前
面试中关于 c++ async 的高频面试问题有哪些?
c++·面试
Abona1 小时前
C语言嵌入式全栈Demo
linux·c语言·面试
编码者卢布2 小时前
【Azure Storage Account】Azure Table Storage 跨区批量迁移方案
后端·python·flask
她说..5 小时前
策略模式+工厂模式实现审批流(面试问答版)
java·后端·spring·面试·springboot·策略模式·javaee
梦梦代码精5 小时前
开源、免费、可商用:BuildingAI一站式体验报告
开发语言·前端·数据结构·人工智能·后端·开源·知识图谱
李慕婉学姐6 小时前
【开题答辩过程】以《基于Spring Boot的疗养院理疗管理系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·spring boot·后端
tb_first6 小时前
SSM速通2
java·javascript·后端
一路向北⁢6 小时前
Spring Boot 3 整合 SSE (Server-Sent Events) 企业级最佳实践(一)
java·spring boot·后端·sse·通信
风象南7 小时前
JFR:Spring Boot 应用的性能诊断利器
java·spring boot·后端
爱吃山竹的大肚肚7 小时前
微服务间通过Feign传输文件,处理MultipartFile类型
java·spring boot·后端·spring cloud·微服务