golang 子协程退出影响进程示例

360 阅读2分钟

在 golang 中可以运行成千上万个 goroutine,你不必担心,因为同样的线程在进程中,如果运行那么多数量,则可能引发问题,因为 golang 中的协程是 golang 的 runtime 在管理,尽管协程也存在调度和上下文切换,但在用户态即可完成。

不过需要注意一点的是,在 golang 中并发运行多个 goroutine 时,应该做好 故障恢复,否则会影响进程也退出。

以下就是个影响示例:

package main

import (
   "fmt"
   "sync"
   "time"
)

var wg sync.WaitGroup

/*
time: 2024-07-28T17:31:37.8146231+08:00, task: [0] is running...
task: 0 exit.
time: 2024-07-28T17:31:37.8146231+08:00, task: [1] is running...
task: 1 exit.
time: 2024-07-28T17:31:37.8146231+08:00, task: [3] is running...
task: 7 exit.
time: 2024-07-28T17:31:37.8146231+08:00, task: [2] is running...
task: 2 exit.
task: 8 exit.
task: 5 exit.
main exit.
panic: cannot handle task id = 5
*/
func main() {

   tasksCnt := 10
   for i := 0; i < tasksCnt; i++ {
      wg.Add(1)
      go gogo(i)
   }

   wg.Wait()
   fmt.Println("main exit.")
}

func gogo(idx int) {
   defer func() {
      defer wg.Done()
      fmt.Printf("task: %d exit.\n", idx)
   }()
   fmt.Printf("time: %v, task: [%d] is running...\n", time.Now().Format(time.RFC3339Nano), idx)
   if idx == 5 {
      panic(fmt.Sprintf("cannot handle task id = %d\n", idx))
   }

}

从结果可以看出,当 idx == 5 时会触发 panic,此时 idx = 5 的协程就退出了,而其他协程都还没运行结束,最后进程自己也挂了,这就是 子协程异常退出会影响 进程 退出。

所以,如果我们在子协程中做好 try-catch,异常子协程的问题就不会影响到进程,以下是示例:

package main

import (
   "fmt"
   "sync"
   "time"
)

var wg sync.WaitGroup

/*
time: 2024-07-28T17:42:50.8298977+08:00, task: [4] is running...
task: 4 exit.
time: 2024-07-28T17:42:50.8304225+08:00, task: [2] is running...
task: 2 exit.
time: 2024-07-28T17:42:50.8304225+08:00, task: [6] is running...
task: 6 exit.
time: 2024-07-28T17:42:50.8304225+08:00, task: [5] is running...
task: 5 exit.
Recover err, cannot handle task id = 5

time: 2024-07-28T17:42:50.8304225+08:00, task: [0] is running...
task: 0 exit.
time: 2024-07-28T17:42:50.8298977+08:00, task: [1] is running...
task: 1 exit.
time: 2024-07-28T17:42:50.8304225+08:00, task: [7] is running...
task: 7 exit.
time: 2024-07-28T17:42:50.8298977+08:00, task: [9] is running...
task: 9 exit.
time: 2024-07-28T17:42:50.8304225+08:00, task: [8] is running...
task: 8 exit.
time: 2024-07-28T17:42:50.8304225+08:00, task: [3] is running...
task: 3 exit.
main exit.
*/
func main() {

   tasksCnt := 10
   for i := 0; i < tasksCnt; i++ {
      wg.Add(1)
      go gogo(i)
   }

   wg.Wait()
   fmt.Println("main exit.")
}

func gogo(idx int) {
   defer func() {
      if err := recover(); err != nil {
         fmt.Println("Recover err,", err)
      }
   }()

   defer func() {
      defer wg.Done()
      fmt.Printf("task: %d exit.\n", idx)
   }()
   fmt.Printf("time: %v, task: [%d] is running...\n", time.Now().Format(time.RFC3339Nano), idx)
   if idx == 5 {
      panic(fmt.Sprintf("cannot handle task id = %d\n", idx))
   }

}