Go 知识点(14) — 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而掛掉?
package mainimport ("fmt""time"
)func main() {// 協程 Ago func() {for {fmt.Println("協程 A")}}()// 協程 Bgo func() {time.Sleep(1 * time.Microsecond) // 確保 協程 A 先運行起來panic("協程 B panic")}()time.Sleep(10 * time.Second) // 充分等待協程 B 觸發 panic 完成和協程 A 執行完畢fmt.Println("main end")
}
輸出結果:
協程 A
協程 A
協程 A
協程 A
協程 A
協程 A
協程 A
協程 A
協程 A
協程 A
panic: 協程 B panicgoroutine 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?
精簡上面的代碼如下:
package mainimport ("fmt""time"
)func main() {defer func() {if err := recover(); err != nil {fmt.Printf("panic err is %s", err)}}()// 協程 Bgo func() {panic("協程 B panic")}()time.Sleep(1 * time.Second) // 充分等待協程 B 觸發 panic 完成fmt.Println("main end")
}
我們開啟 1 個協程 B,并在主協程中增加 recover 機制,嘗試在主協程中捕獲協程 B 觸發的 panic , 但是結果未能如愿。 打印結果如下:
panic: 協程 B panicgoroutine 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 并沒有生效,所以我們可以下結論:
哪個協程發生 panic,就需要在哪個協程自身中 recover 。
改成如下代碼,可以正常 recover。
package mainimport ("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")
}
輸出結果:
panic err is panic
main end
所以結論如下:
協程A發生 panic ,協程B無法 recover 到協程A的 panic ,只有協程自己內部的 recover 才能捕獲自己拋出的 panic 。
3. 業務開發實踐
package mainimport ("fmt""sync""time"
)// 該函數的參數為多個業務邏輯函數,且函數個數為變長參數
func allTasks(tasks ...func()) {var wg sync.WaitGroupfor _, t := range tasks {wg.Add(1) // 每啟動一個協程等待組加 1go func(f func()) { // 匿名函數的參數為業務邏輯函數defer func() {// 在每個協程內部接收該協程自身拋出來的 panicif err := recover(); err != nil {fmt.Println("defer", err)}wg.Done() // 每個協程結束時給 等待組減 1}()f() // 業務函數調用執行}(t) // 將當前的業務函數名傳遞給協程}wg.Wait()}// 業務邏輯 A
func A() {fmt.Println("A func begin")panic("error A")
}// 業務邏輯 B
func B() {fmt.Println("B func begin")
}func main() {allTasks(A, B) // 將業務邏輯函數名 A B 傳遞給封裝好的處理函數time.Sleep(1 * time.Second)fmt.Println("main end")
}
輸出結果
B func begin
A func begin
defer error A
main end
這樣我們就實現了一個通用的并發處理邏輯,每次調用我們只需要把業務邏輯的函數傳入即可,不用每次自己單獨編寫一套并發控制邏輯;同時調用邏輯 B 就不會因為調用邏輯 A 的 panic 而掛掉了,容錯率更高。在業務開發中我們可以參考這種實現方式。
package mainimport (
"fmt"
"sync"
)
var wg sync.Waitgroupfunc div(num int) {
defer func() {
err := recover()
if err != nil {
fmt.Println(err) }
wg.Done()
}()
fmt.Printf("10/%d=%d\n", num, 10/num)
}
func main() {
for i:=0; i<10;i++ {
wg.Add(i)
go div(i)
}
wg.Wait()
}
總結
以上是生活随笔為你收集整理的Go 知识点(14) — Go 多协程(单个协程触发panic会导致其它所有协程挂掉,每个协程只能捕获到自己的 panic 不能捕获其它协程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022-2028年中国水基胶行业市场深
- 下一篇: 2022-2028年中国水处理分离膜行业