In Go’s concurrent programming, goroutines serve as a lightweight thread implementation. However, if a panic occurs within a goroutine and remains uncaught, it will crash the entire program. Through concrete examples, this article compares scenarios with and without recover to explore how to properly handle panics in goroutines.
Problem Background
Consider the following code scenario:
package main
import (
"fmt"
"time"
)
func sayHello() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("hello,world")
}
}
func test() {
var myMap map[int]string
myMap[0] = "golang" // 这里会触发 panic: assignment to entry in nil map
}
func main() {
go sayHello()
go test()
for i := 0; i < 10; i++ {
fmt.Println("main() ok=", i)
time.Sleep(time.Second)
}
}
In this example, the test() function attempts to write data to an uninitialized map, which triggers a panic. Let’s analyze both scenarios separately.
Scenario 1: Without recover (commenting out defer func)
When the panic inside the test() function is not caught:
Execution Flow
1. The main function starts two goroutines: sayHello() and test()
2. sayHello() executes normally, printing “hello,world” once per second
3. test() immediately triggers a panic: panic: assignment to entry in nil map
4. The entire program crashes and exits, including the main goroutine and all other goroutines
Execution result: as shown in Figure 1

/app/go-atguigu/channel-details02 # go run main.go
main() ok= 0
panic: assignment to entry in nil map
goroutine 20 [running]:
main.test()
/app/go-atguigu/channel-details02/main.go:17 +0x1e
created by main.main in goroutine 1
/app/go-atguigu/channel-details02/main.go:22 +0x2a
exit status 2
Problem Analysis
– Once a goroutine experiences an uncaught panic, the entire process terminates
– Even if other goroutines (like sayHello()) are running normally, they are forced to stop
– The main thread’s loop cannot continue executing either
– This is catastrophic in a production environment and may cause service unavailability
Scenario 2: Using recover (enabling defer func)
By adding the defer + recover mechanism inside the [test() function:
func test() {
defer func() {
if err := recover(); err != nil {
fmt.Println("test() 发生错误:", err)
}
}()
var myMap map[int]string
myMap[0] = "golang" // 触发 panic,但会被 recover 捕获
}
Execution Flow
1. The main function starts two goroutines: sayHello() and [test()
2. test() triggers a panic, but it is caught by recover()
3. recover() returns the panic value, and the program continues executing
4. sayHello() continues to run normally, unaffected
5. The main thread’s loop also finishes executing normally
Execution result: as shown in Figure 2

/app/go-atguigu/channel-details02 # go run main.go
main() ok= 0
test() error occurred: assignment to entry in nil map
main() ok= 1
hello,world
main() ok= 2
hello,world
main() ok= 3
hello,world
main() ok= 4
hello,world
main() ok= 5
hello,world
main() ok= 6
hello,world
main() ok= 7
hello,world
main() ok= 8
hello,world
main() ok= 9
hello,world
/app/go-atguigu/channel-details02 #
Advantage Analysis
– Fault isolation: A panic in a single goroutine does not affect other goroutines
– Program stability: The main thread and other coroutines can continue running normally
– Error handling: Error logs can be recorded to facilitate subsequent troubleshooting
– Graceful degradation: Even if some features fail, core features can remain available
How the recover Mechanism Works
Role of defer
The defer statement delays the execution of a function until just before the current function returns. Whether the function returns normally or exits due to a panic, the code inside the defer will execute.
Role of recover
recover() is a built-in function that is only effective when called within a defer function:
– If recover() is called during normal execution flow, it returns nil
– If the current goroutine is panicking, recover() captures the panic value and stops the panic from propagating
– Once captured, the program continues execution from where the panic occurred (effectively returning after completing the rest of the current function)
Key Points
1. Must be used within defer: Calling recover() directly in regular code has no effect
2. Only effective in the current goroutine: Each goroutine needs its own recover mechanism
3. Can only be caught once: Once recover catches a panic, that panic is considered handled
4. Does not affect other goroutines: A panic in one goroutine does not automatically propagate to other goroutines
Best Practices
1. Add recover to important goroutines
func safeGoroutine(fn func()) {
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("goroutine panic: %v\n", err)
// 可以记录日志、发送监控告警等
}
}()
fn()
}()
}
2. Application in Web Servers
func handler(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("handler panic: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
// 处理请求的逻辑
}
3. Avoid Overuse
– Do not abuse recover to mask real bugs
– Errors should be caught and handled at the appropriate level
– For predictable errors, prefer returning an error mechanism over using panic
Conclusion
In Go’s concurrent programming, correctly using the recover mechanism is key to ensuring program stability:
– Without recover: A panic in a single goroutine will crash the entire program, affecting all running coroutines
– With recover: Faults can be isolated, ensuring the normal execution of other goroutines and the main thread
It is recommended to add the recover mechanism to all started goroutines, especially in production environments. This ensures that even if a coroutine encounters an issue, it will not affect the overall availability of the service. Additionally, reasonably log and analyze captured panic information to promptly detect and fix potential issues.

