如何優雅地處理 Goroutines 中的錯誤?
在使用 goroutines 的時候,你肯定遇到過需要處理不同 goroutine 裏面的錯誤的情況,這裏跟大家分享幾點經驗之談,也歡迎大家討論。
❌ 在 Waitgroup 中處理
有些開發者可能會使用下面的方式:
package main
import (
"errors"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(3)
var err error
go errorSpawning(3*time.Second, &wg, &err)
go errorSpawning(2*time.Second, &wg, &err)
go errorSpawning(4*time.Second, &wg, &err)
wg.Wait()
if err != nil {
panic(err)
}
}
func errorSpawning(delay time.Duration, wg *sync.WaitGroup, err *error) {
defer wg.Done()
time.Sleep(delay)
fmt.Println("Error spawned after", delay)
*err = errors.New("test error")
}
我們運行上面的代碼看一下:
go run main.go
輸出是:
Error spawned after 2s
Error spawned after 3s
Error spawned after 4s
panic: test error
goroutine 1 [running]:
main.main()
/Users/neversitup/Works/go122/main.go:23 +0x168
exit status 2
這段代碼看起來很亂,因爲我們在引用該 Error 的 3 個函數中共享同一個錯誤。而且我們還需要等待所有 goroutines 結束才能拿到最終的錯誤。
✅ 使用 Channel 來處理
在筆者看來,可以 goroutines 中的通道理解爲一種可以發射或接收信號的傳感器。然後,我會將錯誤視爲信號。當傳感器收到信號(錯誤)時,它會通知我們已經收到信號,然後我們就可以利用這些信號啓動進一步的流程。因此,我可以通過以下方法將我的 goroutines 改爲使用 channel 來傳遞並處理錯誤。
func main() {
errCh := make(chan error, 3)
go errorSpawning(3*time.Second, errCh)
go errorSpawning(2*time.Second, errCh)
go errorSpawning(4*time.Second, errCh)
for err := range errCh {
if err != nil {
panic(err)
}
}
}
func errorSpawning(delay time.Duration, errCh chan<- error) {
time.Sleep(delay)
fmt.Println("Error spawned after", delay)
errCh <- errors.New("test error")
}
只需聲明 errCh(錯誤通道),通道大小等於需要處理的 goroutines 數量。在本例中,errCh 的大小爲 3。
然後將 errCh 作爲 chan<- error 傳遞到函數中(該符號表示 errCh 在該函數中只是一個接收器)。再用一個 for 循環來捕捉錯誤信號,循環的 channel 爲 errCh。
使用這種寫法,如果有一個 goroutines 遇到錯誤,main() 函數就會崩潰。無需等待所有 goroutines 全部結束。
運行測試看看效果:
go run main.go
Error spawned after 2s
panic: test error
goroutine 1 [running]:
main.main()
/Users/neversitup/Works/go122/main.go:18 +0x130
exit status 2
請注意:這段代碼僅作演示。在實際項目中,如果在傳遞通道的函數中未出現錯誤,則需要在未出現錯誤時將其賦值爲 nil,以防止 goroutine hang 住。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/tzUzoyAaNq-59PbwBlvqdg