Go的协程使用起来很方便,但是有有些坑需要注意: 1. 使用goroutine时加上panic recovery机制,避免服务直接不可用 2. 不要创建都无法退出的goroutine,会导致goroutine 泄漏


启动goroutine时加上panic recovery机制

错误示例

使用协程不recover捕获,会导致主程序挂掉

1
2
3
4
5
6
7
8
9
func (a *API) Recover(c *gin.Context) {
	go doJob()
	response.SuccessJSONData(c, nil)
}

func doJob() {
	channel := []uint8{1, 2, 3, 4}
	fmt.Println(channel[10])
}
1
2
3
4
5
6
7
8
panic: runtime error: index out of range [10] with length 4

goroutine 36 [running]:
go-study/internal/api/restful/demo.doJob()
        /data/go-project/src/go-study/internal/api/restful/demo/goroutine.go:17 +0x1d
created by go-study/internal/api/restful/demo.(*API).Recover
        /data/go-project/src/go-study/internal/api/restful/demo/goroutine.go:11 +0x29
exit status 2

正确做法

切记:只要有用到协程的地方,都需要捕获panic错误

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (a *API) Recover(c *gin.Context) {
	goWithRecover(doJob)
	response.SuccessJSONData(c, nil)
}

func doJob() {
	channel := []uint8{1, 2, 3, 4}
	fmt.Println(channel[10])
}

func goWithRecover(fn func()) {
	go runSafe(fn)
}

func runSafe(fn func()) {
	defer func() {
		if p := recover(); p != nil {
			fmt.Println("panic recover", "panic", p, "stack", string(debug.Stack()))
		}
	}()

	fn()
}

不要创建都无法退出的goroutine

协程数量会一直增加,到最后肯能就OOM

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func (a *API) Block(c *gin.Context) {
	doBlock()
	response.SuccessJSONData(c, runtime.NumGoroutine())
}

func doBlock() {
	ch := make(chan bool, 0)
	goWithRecover(func() {
		fmt.Println("异步任务做一些操作")
		<-ch
	})
}

如果在测试阶段发现问题,可以使用uber开源项目goleak

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import (
	"testing"

	"go.uber.org/goleak"
)

func TestBlock(t *testing.T) {
	defer goleak.VerifyNone(t)
	doBlock()
}

测试用例