Golang恐慌崩溃预防

Posted

技术标签:

【中文标题】Golang恐慌崩溃预防【英文标题】:Golang panic crash prevention 【发布时间】:2014-10-10 23:17:58 【问题描述】:

在 Golang 中,没有恢复的恐慌会导致进程崩溃,因此我最终将以下代码 sn-p 放在每个函数的开头:

defer func() 
    if err := recover(); err != nil 
        fmt.Println(err)
    
()

只是为了防止我的程序崩溃。现在我想知道,这真的是要走的路吗?因为我觉得到处都放同样的代码看起来有点奇怪。

在我看来,Java 方式将异常冒泡到调用函数,直到 main 函数是控制异常/恐慌的更好方法。我知道这是 Go 的设计,但是像 Go 那样立即使进程崩溃有什么好处?

【问题讨论】:

您不应该将恐慌视为 Go 中的 Java 异常。它们很少使用。在 Java 中,它们用于指示任何类型的错误情况。另一方面,在 Go 中,指示错误的习惯用法是将错误作为最后一个返回值返回(例如,参见 os.Open)。因此,恐慌被保留用于应该像 nil 指针取消引用那样使程序崩溃的情况。 是的,但是为了编写一个健壮的服务器程序员,尤其是一个可扩展的插件或拦截器系统,你真的不应该让插件或拦截器轻易地让你的主服务器崩溃,对吗? 正确,可能需要这种行为(Go 的 net/http 服务器使用恢复来捕获恐慌的 goroutine),但同时你没有问这个问题。恐慌/延迟/恢复是例外 - 仅在需要时使用它们,不再使用。 谢谢@elithrar,我知道在使用恐慌/恢复时我应该小心。但是,如果我使用其他人的图书馆,那将是我无法控制的。是的,为了安全起见,我可以在我的每个功能上执行延迟/恢复,这就是我现在所做的。我只是想知道,与 Java 的冒泡模型相比,如此容易使进程崩溃有什么优势? @synful,是的,同意,但是,在 Java RuntimeException 或 NullPointerException 中不会使进程崩溃。我看不出有什么优势使进程崩溃而不是在 main 之前冒泡给调用者。 【参考方案1】:

我认为panic 与异常不同。您可以处理异常并继续运行例程。而恐慌会导致终止当前例程,您不能跳过它。 异常通常由操作系统发出并导致运行相关例程。相反,恐慌由程序员手动发出并导致退出goroutine。 您可以为函数中的每段代码定义多个异常。恐慌恢复机制适用于整个功能。 Exception 被设计用来处理,而 Panic 被设计用来终止和恐慌恢复机制似乎只是控制终止的一个技巧。

因此异常处理与错误处理相当。 但是如何在 Golang 中利用它呢?

我将描述我的用例来回答您的问题。

有两种类型的阻塞错误,Panic 和 Fatal。您无法从致命错误中恢复。 有时您需要终止该进程。但有时您需要重新启动它。 我使用recover() 机制从恐慌错误中恢复,以关闭当前的 goroutine 并重新启动主要功能。 所以我必须小心错误类型。某些情况需要发出致命错误,例如缺少必要的配置文件。有时重启应用程序是合理的。考虑一下您希望在崩溃后重新启动应用程序的所有情况。如:

服务过载(导致 DoS) 缺少 DBMS 来自其他使用的 go 包的意外错误 例如崩溃imagick进程 等等

所以,recover() 对我来说非常有益。它提供了在退出之前清楚地关闭进程的机会。

旁注:您可以开发一个 bootstrapper 应用程序,它将主应用程序作为独立进程运行。如果该进程异常退出,它必须重新运行主应用程序。 使用日志记录以便在保持应用运行的同时进行调试。

【讨论】:

【参考方案2】:

通常,即使有异常,您也会在“FaultBarrier”处捕获它们。它通常是产生所有新线程的地方。关键是要捕获并记录意外故障。

在 Go 中,您对所有预期的失败都使用返回值。您工作的框架通常会有一个故障屏障来捕获会话(即:通常是一个 http 事务)并记录问题。我看到恢复发生的唯一其他地方是诸如非幂等关闭函数之类的事情。如果您无法判断某物是否已经关闭但知道它必须关闭,那么您最终可能会进行恢复,以便忽略第二次关闭恐慌,而不是使您正在做的所有事情都失败直到 FaultBarrier。

【讨论】:

【参考方案3】:

只有在您确切知道原因的情况下,您才应该从恐慌中恢复过来。 Go 程序在两种情况下会出现恐慌:

程序逻辑错误(例如 nil 指针取消引用或越界数组或切片访问) 来自您的代码或您的代码调用的代码的故意恐慌(使用 panic(...) 调用)

在第一种情况下,崩溃是合适的,因为这意味着您的程序已进入错误状态并且不应继续执行。在第二种情况下,您应该只在预期的情况下从恐慌中恢复过来。解释这一点的最好方法就是简单地说它非常罕见,如果你看到它就会知道这种情况。我几乎可以肯定,无论您编写什么代码,您都不需要从恐慌中恢复。

【讨论】:

我正在做的是我正在编写一个等待传入连接的服务器。当传入连接连接时,服务器会启动一个 go 例程来处理该传入连接。如果任何 goroutine 出现恐慌,我不希望服务器崩溃。我什至不在乎 go 例程中发生了什么。如果失败了,那就失败了。在这种情况下,似乎我必须确保我必须在 go 例程的代码中恢复任何可能的恐慌。我是对的,还是我错过了什么? 如果您的程序出现恐慌,则表明存在编程错误。您想要忽略的任何正常错误(如网络超时或格式错误的请求)都将以函数返回的错误值的形式出现,而不是恐慌。 @ElgsQianChen panic() 在 go 中以与 C 中的 NULL 指针取消引用相同的方式使您的程序崩溃。在这两种情况下,您都不会真的希望您的程序继续执行。 我也遇到了同样的情况。在我的服务器上,我从 WebSocket 接收 JSON 数据。然后我对其进行解码并对其进行类型断言,假设数据是正确的。对于正常使用,这不会失败,但是如果数据已被更改怎么办?恢复恐慌不是确保黑客(例如)不能以这种方式使我的服务器崩溃的最佳方法吗? @sebcap26,有一种方法可以在不引起恐慌的情况下进行类型断言。如果您执行val, ok := interfaceValue.(Type),则类型断言的第二个返回值(即放在ok 中的值)将是一个布尔值,指示断言是否成功。使用它,如果断言失败,它只会使ok false 而不是恐慌。

以上是关于Golang恐慌崩溃预防的主要内容,如果未能解决你的问题,请参考以下文章

在 Golang 中捕捉恐慌

Golang 模拟上下文恐慌

在golang中输入正确但输出恐慌

golang 展示恐慌,推迟和恢复结合使用的完整例子

golang - mysql 恐慌:运行时错误:无效的内存地址或 nil 指针取消引用

Golang 中的安全关闭连接