在併發編程中,錯誤處理可能難以正確運行。有時候,我們花了很多時間思考我們的各種流程將如何共享信息和協調,卻忘記考慮如何優雅地處理錯誤。Go避開了流行的錯誤異常模型,Go認為錯誤處理非常重要,並且在開發程序時,我們應該像關注算法一樣關注它。本著這種精神,讓我們來看看在處理多個併發時我們如何做到這一點。
思考錯誤處理時最根本的問題是,“應該由誰負責處理錯誤?”在某些情況下,程序需要停止傳遞堆棧中
的錯誤,並將它們處理掉,這樣的操作應該何時執行呢?
在並發進程中,這樣的問題變得愈發覆雜。因為一個併發進程獨立於其父進程或兄弟進程運行,所以可能
很難推斷出錯誤是如何產生的。
以下是一個例子:
func main() {
// 定義一個匿名函數 checkStatus
// 它接收一個 done channel 以及一組網址,
// 然後回傳一個 http.Response 指針 channel
checkStatus := func(
done <-chan interface{},
urls ...string,
) <-chan *http.Response {
// 創建一個 http.Response 指針 channel
responses := make(chan *http.Response)
go func() { // 開始一個新的 goroutine
defer close(responses) // 確保 responses channel 會在 goroutine 完成時關閉
for _, url := range urls {
resp, err := http.Get(url) // 對該網址發送 GET 請求
if err != nil {
fmt.Println(err) // <1> 如果有錯誤就輸出錯誤
continue // 繼續下一次循環
}
select {
case <-done: // 如果 done channel 收到信號,則退出 goroutine
return
case responses <- resp: // 否則,將 http.Response 放入 responses channel
}
}
}()
return responses // 回傳 http.Response channel
}
// 創建一個 done channel
done := make(chan interface{})
defer close(done) // 確保 main 函數結束時 done channel 會被關閉
// 定義兩個網址
urls := []string{"https://www.google.com", "https://badhost"}
// 對每個網址調用 checkStatus 函數並迭代返回的 responses
for response := range checkStatus(done, urls...) {
fmt.Printf("Response: %v\n", response.Status) // 輸出每個網址的回應狀態
}
}
在這個例子裡面,我們並沒有對錯誤進行任何處理,只是把它印出來。
但事實上我們沒辦法把錯誤印出來當作是錯誤處理
所以我們來改善這個程式如下:
使用一個 Result 結構體,用於將錯誤和響應捆綁在一起,從而允許在單個channel中同時傳輸錯誤和響應
func main() {
// 定義一個結構體 Result,其中包括一個 Error 和一個 http.Response
type Result struct { // <1>
Error error
Response *http.Response
}
// 定義 checkStatus 函數
// 它接收一個 done channel 和一組網址,並回傳一個 Result channel
checkStatus := func(done <-chan interface{}, urls ...string) <-chan Result { // <2>
results := make(chan Result)
go func() {
defer close(results) // 確保在 goroutine 完成時關閉 results channel
for _, url := range urls {
var result Result
resp, err := http.Get(url) // 對該網址發送 GET 請求
result = Result{Error: err, Response: resp} // <3> 創建一個 Result 實例並設置 Error 和 Response
select {
case <-done: // 如果 done channel 收到信號,則退出 goroutine
return
case results <- result: // <4> 將 Result 放入 results channel
}
}
}()
return results // 回傳 results channel
}
done := make(chan interface{})
defer close(done) // 確保 main 函數結束時關閉 done channel
urls := []string{"https://www.google.com", "https://badhost"}
// 對每個網址調用 checkStatus 函數並迭代返回的 results
for result := range checkStatus(done, urls...) {
if result.Error != nil { // <5> 如果 Result 中的 Error 不是 nil,輸出錯誤並繼續
fmt.Printf("error: %v", result.Error)
continue
}
fmt.Printf("Response: %v\n", result.Response.Status) // 輸出每個網址的回應狀態
}
}