Go 多协程(单个协程触发panic会导致所有协程挂掉,每个协程只能捕获到自己的 panic 不能捕获其它协程)

一、概念

在多协程并发环境下,我们常常会碰到以下两个问题。假设我们现在有 2 个协程,我们叫它们协程 A 和 B 。

【问题1】如果协程 A 发生了 panic ,协程 B 是否会因为协程 A 的 panic 而挂掉?
【问题2】如果协程 A 发生了 panic ,协程 B 是否能用 recover 捕获到协程 A 的 panic ?
答案分别是:会、不能。

1.【问题1】

  • 【问题1】如果协程 A 发生了 panic ,协程 B 是否会因为协程 A 的 panic 而挂掉?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    // 协程 A
    go func() {
        for {
            fmt.Println("协程 A")
        }
    }()
 
    // 协程 B
    go func() {
        time.Sleep(1 * time.Microsecond) // 确保 协程 A 先运行起来
        panic("协程 B panic")
    }()
 
    time.Sleep(10 * time.Second) // 充分等待协程 B 触发 panic 完成和协程 A 执行完毕
    fmt.Println("main end")
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
协程 A
协程 A
协程 A
协程 A
协程 A
协程 A
协程 A
协程 A
协程 A
协程 A
panic: 协程 B panic
 
goroutine 6 [running]:
main.main.func2()
    /home/wohu/GoCode/src/hello.go:19 +0x46
created by main.main
    /home/wohu/GoCode/src/hello.go:17 +0x51
exit status 2

可以看到,在协程 B 触发 panic 之后,协程 A 并没有继续打印,并且主协程的 main end 也没有打印出来,充分说明了在 B 协程触发 panic 之后,

在 A 协程也会因此挂掉,且主协程也会挂掉

2.【问题2】

  • 【问题2】如果协程 A 发生了 panic ,协程 B 是否能用 recover 捕获到协程 A 的 panic ?

精简上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Printf("panic err is %s", err)
        }
    }()
 
    // 协程 B
    go func() {
        panic("协程 B panic")
    }()
 
    time.Sleep(1 * time.Second) // 充分等待协程 B 触发 panic 完成
    fmt.Println("main end")
}

我们开启 1 个协程 B,并在主协程中增加 recover 机制,尝试在主协程中捕获协程 B 触发的 panic , 但是结果未能如愿。

打印结果如下:

1
2
3
4
5
6
7
8
panic: 协程 B panic
 
goroutine 5 [running]:
main.main.func2()
    /home/wohu/GoCode/src/hello.go:18 +0x39
created by main.main
    /home/wohu/GoCode/src/hello.go:17 +0x59
exit status 2

从结果可以看到, recover 并没有生效,所以我们可以下结论:

1
哪个协程发生 panic,就需要在哪个协程自身中 recover 。

改成如下代码,可以正常 recover。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
 
import (
    "fmt"
    "time"
)
 
func main() {
 
    go func() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Printf("panic err is %s\n", err)
            }
        }()
        panic("panic")
    }()
 
    time.Sleep(1 * time.Second) // 充分等待协程触发 panic 完成
    fmt.Println("main end")
}

输出结果:

1
2
panic err is panic
main end

所以结论如下:

协程A发生 panic ,协程B无法 recover 到协程A的 panic ,只有协程自己内部的 recover 才能捕获自己抛出的 panic 。



感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮。本文欢迎各位转载,但是转载文章之后必须在文章页面中给出作者和原文连接
posted @   南昌拌粉的成长  阅读(1456)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
历史上的今天:
2021-06-20 struct解析为json
2021-06-20 golang panic和recover
2021-06-20 fmt.sprintf,fprintf区别
2021-06-20 web界面不显示内容排查思路
点击右上角即可分享
微信分享提示