没有不值得去解决的问题,也没有不值得去学习的技术!

Catching Panics in Goroutines With Go’s Recover Mechanism

Execution result: as shown in Figure 1

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:

Go
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

Execution result: as shown in Figure 1
Go
/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:

Go
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

Execution result: as shown in Figure 2
Plaintext
/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

Go
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

Go
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.

系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系