Panic and recover have been introduced before. The following of the current article will introduce some (good and bad) panic/recover use cases.
This should be the most popular use case of panic/recover. The use case is used commonly in concurrent programs, especially client-server programs.
An example:package main
import "errors"
import "log"
import "net"
func main() {
listener, err := net.Listen("tcp", ":12345")
if err != nil {
log.Fatalln(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Println(err)
}
// Handle each client connection
// in a new goroutine.
go ClientHandler(conn)
}
}
func ClientHandler(c net.Conn) {
defer func() {
if v := recover(); v != nil {
log.Println("capture a panic:", v)
log.Println("avoid crashing the program")
}
c.Close()
}()
panic(errors.New("just a demo.")) // a demo-purpose panic
}
Start the server and run telnet localhost 12345
in another terminal,
we can observe that the server will not crash down
for the panics created in each client handler goroutine.
If we don't recover the potential panic in each client handler goroutine, the potential panic will crash the program.
package main
import "log"
import "time"
func shouldNotExit() {
for {
// Simulate a workload.
time.Sleep(time.Second)
// Simulate an unexpected panic.
if time.Now().UnixNano() & 0x3 == 0 {
panic("unexpected situation")
}
}
}
func NeverExit(name string, f func()) {
defer func() {
if v := recover(); v != nil {
// A panic is detected.
log.Println(name, "is crashed. Restart it now.")
go NeverExit(name, f) // restart
}
}()
f()
}
func main() {
log.SetFlags(0)
go NeverExit("job#A", shouldNotExit)
go NeverExit("job#B", shouldNotExit)
select{} // block here for ever
}
panic
/recover
Calls to Simulate Long Jump StatementsSometimes, we can use panic/recover as a way to simulate crossing-function long jump statements and crossing-function returns, though generally this way is not recommended to use. This way does harm for both code readability and execution efficiency. The only benefit is sometimes it can make code look less verbose.
In the following example, once a panic is created in an inner function, the execution will jump to the deferred call.package main
import "fmt"
func main() {
n := func () (result int) {
defer func() {
if v := recover(); v != nil {
if n, ok := v.(int); ok {
result = n
}
}
}()
func () {
func () {
func () {
// ...
panic(123) // panic on succeeded
}()
// ...
}()
}()
// ...
return 0
}()
fmt.Println(n) // 123
}
panic
/recover
Calls to Reduce Error Checksfunc doSomething() (err error) {
defer func() {
err, _ = recover().(error)
}()
doStep1()
doStep2()
doStep3()
doStep4()
doStep5()
return
}
// In reality, the prototypes of the doStepN functions
// might be different. For each of them,
// * panic with nil for success and no needs to continue.
// * panic with error for failure and no needs to continue.
// * not panic for continuing.
func doStepN() {
...
if err != nil {
panic(err)
}
...
if done {
panic(nil)
}
}
The above code is less verbose than the following one.
func doSomething() (err error) {
shouldContinue, err := doStep1()
if !shouldContinue {
return err
}
shouldContinue, err = doStep2()
if !shouldContinue {
return err
}
shouldContinue, err = doStep3()
if !shouldContinue {
return err
}
shouldContinue, err = doStep4()
if !shouldContinue {
return err
}
shouldContinue, err = doStep5()
if !shouldContinue {
return err
}
return
}
// If err is not nil, then shouldContinue must be true.
// If shouldContinue is true, err might be nil or non-nil.
func doStepN() (shouldContinue bool, err error) {
...
if err != nil {
return false, err
}
...
if done {
return false, nil
}
return true, nil
}
However, usually, this panic/recover use pattern is not recommended to use. It is less Go-idiomatic and less efficient.
And please note that, since a future Go version (probably Go 1.21), apanic(nil)
call will become
eqivalent to panic(new(runtime.PanicNilError))
.
Since then, the above deferred function call should be written as
func doSomething() (err error) {
defer func() {
err, _ = recover().(error)
if e := (*runtime.PanicNilError)(nil); errors.As(err, &e) {
err = nil
}
}()
doStep1()
...
}
The Go 101 프로젝트는 Github 에서 호스팅됩니다. 오타, 문법 오류, 부정확한 표현, 설명 결함, 코드 버그, 끊어진 링크와 같은 모든 종류의 실수에 대한 수정 사항을 제출하여 Go 101을 개선을 돕는 것은 언제나 환영합니다.
주기적으로 Go에 대한 깊이 있는 정보를 얻고 싶다면 Go 101의 공식 트위터 계정인 @go100and1을 팔로우하거나 Go 101 슬랙 채널에j가입해주세요.
reflect
표준 패키지sync
표준 패키지sync/atomic
표준 패키지