常见panic场景
- 数组或切片越界,例如
s := make([]int, 3); fmt.Println(s[5])
会引发 panic: runtime error: index out of range - 空指针调用,例如
var p *Person; fmt.Println(p.Name)
会引发 panic: runtime error: invalid memory address or nil pointer dereference - 过早关闭 HTTP 响应体,例如
resp, err := http.Get(url); defer resp.Body.Close(); if err != nil { return err }
会引发 panic: runtime error: invalid memory address or nil pointer dereference,因为如果 http.Get 出错,resp 可能是 nil - 除以零,例如
x := 0; y := 1 / x
会引发 panic: runtime error: integer divide by zero - 向已经关闭的 channel 发送或接收消息,例如
ch := make(chan int); close(ch); ch <- 1
会引发 panic: send on closed channel - 重复关闭 channel,例如
ch := make(chan int); close(ch); close(ch)
会引发 panic: close of closed channel - 关闭未初始化的 channel,例如
var ch chan int; close(ch)
会引发 panic: close of nil channel - 未初始化的 map,例如
var m map[string]int; m["key"] = 1
会引发 panic: assignment to entry in nil map
recover常用的场景
panic
只会触发当前 Goroutine 的defer
;recover
只有在defer
中调用才会生效;
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
fmt.Println("Before panic")
panic("Something went wrong")
fmt.Println("After panic") // This line will not be executed
}
运行结果
Before panic
Recovered from panic: Something went wrong
defer,recover,panic,goroutine之间有什么联系呢
recover的作用域,recover在什么时候才会起作用
recover什么时候有效
recover未在defer内使用,是不会起任何作用
func main() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
fmt.Println("Before panic")
panic("Something went wrong")
fmt.Println("After panic") // This line will not be executed
}
结果
Before panic
panic: Something went wrong
goroutine 1 [running]:
main.main()
/Users/alan/GolandProjects/design-patterns/main.go:11 +0x78
recover必须搭配defer使用
recover在defer内使用才会起作用
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
fmt.Println("Before panic")
panic("Something went wrong")
fmt.Println("After panic") // This line will not be executed
}
运行结果
Before panic
Recovered from panic: Something went wrong
recove的作用域
recover在父协程
举个例子,一般使用chan的时候都是要在发送测关闭chan,我们在接受者通过控制超时
让函数提前退出,子协程一秒钟后退出
func main() {
test()
time.Sleep(time.Second * 10)
}
func test() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
var ch = make(chan int)
go get_data(ch)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
defer close(ch)
select {
case <-ch:
return
case <-ctx.Done():
return
}
}
func get_data(ch chan int) {
time.Sleep(time.Second * 2)
ch <- 1
}
结果
我们发现没有recover住panic,子协程在test方法调用退出后,发生了panic,导致整个程序panic挂掉
recover,panic同子协程
相同的例子我们,我们在引起panic的协程内进行recover,结果函数正常recover后程序正常退出
func main() {
test()
time.Sleep(time.Second * 10)
fmt.Println("时间到了 主函数也溜了")
}
func test() {
defer func() {
fmt.Println("test:我先溜了")
}()
var ch = make(chan int)
go get_data(ch)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
defer close(ch)
select {
case <-ch:
return
case <-ctx.Done():
return
}
}
func get_data(ch chan int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
time.Sleep(time.Second * 2)
ch <- 1
}
结果
正确使用close关闭chan
recover在同级函数作用域下起效
引用