面试题答案
一键面试使用panic时对程序执行流程的影响
- 立即停止当前函数执行:当
panic
发生时,当前函数的执行立即停止。例如:
func main() {
fmt.Println("Start")
panic("Something went wrong")
fmt.Println("This line won't be printed")
}
在上述代码中,panic
语句之后的fmt.Println("This line won't be printed")
不会被执行。
2. 逐层展开调用栈:panic
会导致程序从发生panic
的函数开始,逐层展开调用栈(unwind the stack),并执行每一层函数中的defer
语句。例如:
func f1() {
defer fmt.Println("f1 defer")
f2()
}
func f2() {
defer fmt.Println("f2 defer")
panic("panic in f2")
}
func main() {
f1()
}
在这个例子中,f2
中发生panic
后,会先执行f2
中的defer
语句,输出f2 defer
,然后回到f1
执行f1
中的defer
语句,输出f1 defer
。
3. 程序终止:如果panic
没有被recover
捕获,程序最终会终止,并输出panic
信息和调用栈信息。
使用普通错误时对程序执行流程的影响
- 函数返回错误值:普通错误通常通过函数返回值的方式传递。例如,
os.Open
函数打开文件失败时会返回一个错误:
file, err := os.Open("nonexistentfile.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
// 文件操作代码
file.Close()
- 调用者处理错误:调用函数的代码可以检查错误并决定如何处理。通常,不会导致整个程序终止,而是可以进行一些恢复操作,比如重试、记录日志等。
- 程序继续执行:如果调用者正确处理了错误,程序可以继续正常执行后续逻辑。
在资源管理方面的不同表现
- panic时的资源管理:
- 自动执行defer:由于
panic
会逐层展开调用栈并执行defer
语句,在defer
中进行的资源释放操作(如关闭文件句柄、数据库连接等)依然会被执行。例如:
- 自动执行defer:由于
func main() {
file, err := os.Open("test.txt")
if err != nil {
panic(err)
}
defer file.Close()
// 其他操作
panic("Another panic")
}
即使发生panic
,文件依然会在defer
语句中被关闭。
- 未被recover会导致程序终止:如果panic
未被recover
,虽然资源在defer
中释放了,但程序终止可能会导致一些未完成的业务操作丢失,如未提交的数据库事务等。
2. 普通错误时的资源管理:
- 调用者负责释放:调用者在处理错误后,需要手动确保资源的正确释放。例如在打开文件失败时,需要确保文件句柄没有被意外使用,并且可以选择在适当的时候释放资源。
- 程序可控:由于普通错误不会导致程序立即终止,调用者可以根据业务需求,在错误处理中决定是否重新尝试获取资源、关闭已获取的部分资源等,对资源管理更具可控性。
应对方式
- panic的应对:
- 局部捕获:在合适的函数中使用
recover
来捕获panic
,防止程序崩溃。例如:
- 局部捕获:在合适的函数中使用
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("Test panic")
}
- **全局捕获**:在Web服务器等应用中,可以在入口处通过中间件或全局异常处理机制捕获`panic`,记录日志并返回适当的错误响应给客户端,同时确保资源的正确释放。
2. 普通错误的应对: - 错误处理逻辑:在调用可能返回错误的函数后,立即检查错误,并根据错误类型和业务需求进行处理,如重试、回滚事务、释放部分已获取的资源等。 - 错误传递:如果当前函数无法处理错误,可以将错误返回给调用者,由调用者进行更高层次的处理。