Go进阶之panic

panic的中文含义为"恐慌 惊慌".程序发生panic时会结束当前协程.进而触发整个程

序的崩溃.内置函数recover()可以接受panic并使程序回到正轨.

1.panic()函数:

panic是一个内置函数:

源码位置:src/builtin/builtin.go

Go 复制代码
// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated with a non-zero exit code. This
// termination sequence is called panicking and can be controlled by the
// built-in function recover.
//
// Starting in Go 1.21, calling panic with a nil interface value or an
// untyped nil causes a run-time error (a different panic).
// The GODEBUG setting panicnil=1 disables the run-time error.
func panic(v any)

它接受一个任意类型的参数.参数将在程序崩溃时通过另一个内置函数print(args

...Type)打印出来.如果程序返回途中任意一个defer函数执行了recover().那么该参

数也是recover()的返回值.

panic可由程序员显示的通过内置函数触发.Go运行时遇到诸如内存越界之类的问题

也会触发.

2.工作流程:

上面流程中.程序启动了两个协程.如果某个协程执行过程中产生了panic.那么程序将

立即转向执行defer函数.当前函数中的defer执行完毕后继续处理上层函数的defer.

当协程中所有defer处理完成以后.程序退出.

注:

panic会递归执行协程中所有的defer.与正常函数退出时一致.

panic不会处理其他协程中的defer.

当前协程中的defer处理完成后.触发程序退出.

3.源码剖析:

示例:

Go 复制代码
package Concurrent

func compile() {
    panic("aa")
}

通过命令编译代码.可以看到panic语句被编译成 CALL runtime.gopanic(SB).也

就是panic()函数的真身是runtime.gopanic().源码位置为

src/runtime/panic.go.

4.数据结构:

源码位置:src/runtime/runtime2.go

Go 复制代码
type _panic struct {
    argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
    arg  any            // argument to panic
    link *_panic        // link to earlier panic

    // startPC and startSP track where _panic.start was called.
    startPC uintptr
    startSP unsafe.Pointer

    // The current stack frame that we're running deferred calls for.
    sp unsafe.Pointer
    lr uintptr
    fp unsafe.Pointer

    // retpc stores the PC where the panic should jump back to, if the
    // function last returned by _panic.next() recovers the panic.
    retpc uintptr

    // Extra state for handling open-coded defers.
    deferBitsPtr *uint8
    slotsPtr     unsafe.Pointer

    recovered   bool // whether this panic has been recovered
    goexit      bool
    deferreturn bool
}

argp:defer函数参数指针.

arg: panic 的触发参数.即 panic(x) 中的 x(如 panic("error") 中的字符串).

link: panic 链表指针.指向更早触发的 panic(支持嵌套 panic,如 defer 中再次

panic).

startPC: 调用 start 方法时的程序计数器 .

startSP: 调用 start 方法时的栈指针.

sp: 当前栈帧的栈指针(匹配 defer 注册时的 sp,确保执行当前函数的 defer).

lr: 链接寄存器(记录函数返回地址,用于栈帧回溯).

fp: 帧指针(指向当前栈帧底部,辅助定位局部变量).

retpc: 这是 recover 能让程序"恢复执行"的核心------修改执行流到 retpc 指向的位

置 .

deferBitsPtr: 指向开放编码 defer 的执行状态位(标记哪些 defer 已执行).

slotsPtr: 指向开放编码 defer 的函数槽(存储待执行的开放编码 defer 函数).

recovered: 该 panic 是否被 recover() 恢复(true 表示已恢复,终止 panic 流

程).

goexit: 是否是 runtime.Goexit() 触发的 panic(Goexit 本质是特殊 panic).

deferreturn: 是否是 deferreturn 触发的"伪 panic"(仅用于执行 defer,无实

际 panic).

5.gopanic分析:

runtime.gopanic()函数的核心任务是消费协程中的defer链表.当所有的defer处

理完后再触发程序退出.由于defer函数中有可能触发新的panic.即产生新的

runtime.gopanic(),另外panic也有可能被recover()恢复.

源码位置:

src/runtime/panic.go

Go 复制代码
func gopanic(e any) {
    if e == nil {
       if debug.panicnil.Load() != 1 {
          e = new(PanicNilError)
       } else {
          panicnil.IncNonDefault()
       }
    }

    gp := getg()
    if gp.m.curg != gp {
       print("panic: ")
       printpanicval(e)
       print("\n")
       throw("panic on system stack")
    }

    if gp.m.mallocing != 0 {
       print("panic: ")
       printpanicval(e)
       print("\n")
       throw("panic during malloc")
    }
    if gp.m.preemptoff != "" {
       print("panic: ")
       printpanicval(e)
       print("\n")
       print("preempt off reason: ")
       print(gp.m.preemptoff)
       print("\n")
       throw("panic during preemptoff")
    }
    if gp.m.locks != 0 {
       print("panic: ")
       printpanicval(e)
       print("\n")
       throw("panic holding locks")
    }

    var p _panic
    p.arg = e

    runningPanicDefers.Add(1)

    p.start(sys.GetCallerPC(), unsafe.Pointer(sys.GetCallerSP()))
    for {
       fn, ok := p.nextDefer()
       if !ok {
          break
       }
       fn()
    }

    // If we're tracing, flush the current generation to make the trace more
    // readable.
    //
    // TODO(aktau): Handle a panic from within traceAdvance more gracefully.
    // Currently it would hang. Not handled now because it is very unlikely, and
    // already unrecoverable.
    if traceEnabled() {
       traceAdvance(false)
    }

    // ran out of deferred calls - old-school panic now
    // Because it is unsafe to call arbitrary user code after freezing
    // the world, we call preprintpanics to invoke all necessary Error
    // and String methods to prepare the panic strings before startpanic.
    preprintpanics(&p)

    fatalpanic(&p)   // should not return
    *(*int)(nil) = 0 // not reached
}

1).panic(nil)特殊场景:

2).gp:=getg()获取当前goroutine.

3). 安全检查1:禁止在系统栈上触发 panic.

4). 安全检查2:禁止在内存分配期间 panic(mallocing 标记为 1 表示正在分配内存).

5). 安全检查3:禁止在"禁止抢占"期间 panic(preemptoff 非空表示禁止抢占).

6). 安全检查4:禁止在持有运行时锁期间 panic(locks 非 0 表示持有锁).

7). 创建 _panic 实例,初始化核心字段.

8). 统计 panic 期间执行 defer 的计数器(监控/调试用).

runningPanicDefers.Add(1).

9). 初始化 panic 上下文:传入调用 gopanic 时的 PC/SP,关联当前栈帧 . 让 _panic 定位到当前函数的 defer 链表,准备遍历执行 .

10). 核心循环:遍历并执行所有 defer 函数 .

11). 跟踪(trace)相关:若开启了执行跟踪,刷新当前跟踪数据(提升 panic 日志可读性).

12). 所有 defer 执行完毕且未 recover → 触发致命 panic .

13). 致命 panic:打印栈跟踪、终止 Goroutine/程序(不会返回).

6.流程图:

++春风若有怜花意.可否许我再少年.++

++如果大家喜欢我的分享的话.可以关注我的微信公众号++

++念何架构之路++

相关推荐
先跑起来再说1 小时前
Git 入门到实战:一篇搞懂安装、命令、远程仓库与 IDEA 集成
ide·git·后端·elasticsearch·golang·intellij-idea
亓才孓2 小时前
[Properties]写配置文件前,必须初始化Properties(引用变量没执行有效对象,调用方法会报空指针错误)
开发语言·python
傻乐u兔2 小时前
C语言进阶————指针3
c语言·开发语言
两点王爷2 小时前
Java基础面试题——【Java语言特性】
java·开发语言
Swift社区2 小时前
Gunicorn 与 Uvicorn 部署 Python 后端详解
开发语言·python·gunicorn
码农阿豪2 小时前
Flask应用上下文问题解析与解决方案:从错误日志到完美修复
后端·python·flask
码农阿豪2 小时前
Python Flask应用中文件处理与异常处理的实践指南
开发语言·python·flask
岁岁种桃花儿2 小时前
CentOS7 彻底卸载所有JDK/JRE + 重新安装JDK8(实操完整版,解决kafka/jps报错)
java·开发语言·kafka
威迪斯特2 小时前
Flask:轻量级Web框架的技术本质与工程实践
前端·数据库·后端·python·flask·开发框架·核心架构