Go goroutine 并发编程

本文最后更新于:2 年前

对自己创建的 goroutine 负责

  1. 请将是否异步调用的选择权交给调用者,不然很有可能大家并不知道你在这个函数里面使用了 goroutine
  2. 如果你要启动一个 goroutine 请对它负责
    1. 永远不要启动一个你无法控制它退出,或者你无法知道它何时推出的 goroutine
    2. 启动 goroutine 时请加上 panic recovery 机制,避免服务直接不可用
    3. 造成 goroutine 泄漏的主要原因就是 goroutine 中造成了阻塞,并且没有外部手段控制它退出
  3. 尽量避免在请求中直接启动 goroutine 来处理问题,而应该通过启动 worker 来进行消费,这样可以避免由于请求量过大,而导致大量创建 goroutine 从而导致 oom,当然如果请求量本身非常小,没啥问题了

基于 errgroup 实现一个 http server 的启动和关闭 ,以及 linux signal 信号的注册和处理,要保证能够一个退出,全部注销退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
func main() {
group, ctx := errgroup.WithContext(context.Background())

mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello Go")
})

server := http.Server{
Handler: mux,
Addr: ":8889",
}

// 利用无缓冲chan 模拟单个服务错误退出
serverOut := make(chan struct{})
mux.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
serverOut <- struct{}{} // 阻塞
})

// -- 测试 http server 的启动和退出 --

// g1 启动http server服务
// g1 退出后, context 将不再阻塞,g2, g3 都会随之退出
group.Go(func() error {
return server.ListenAndServe()
})

// g2
// g2 退出时,调用了 shutdown,g1 也会退出
group.Go(func() error {
select {
case <-serverOut:
fmt.Println("server closed") // 退出会触发 g.cancel, ctx.done 会收到信号
case <-ctx.Done():
fmt.Println("errgroup exit")
}

timeoutCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
log.Println("shutting down server...")
return server.Shutdown(timeoutCtx)
})

// g3 linux signal 信号的注册和处理
// g3 捕获到 os 退出信号将会退出
// g3 退出后, context 将不再阻塞,g2 会随之退出
// g2 退出时,调用了 shutdown,g1 会退出
group.Go(func() error {
quit := make(chan os.Signal, 1)
// sigint 用户ctrl+c, sigterm程序退出
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

select {
case <-ctx.Done():
return ctx.Err()
case sig := <-quit:
return errors.Errorf("get os exit: %v", sig)
}
})

// 然后 main 函数中的 g.Wait() 退出,所有协程都会退出
err := group.Wait()
fmt.Println(err)
fmt.Println(ctx.Err())
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!