iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
自我挑戰組

Concurrency in go 讀書心得系列 第 14

14.Error handling

  • 分享至 

  • xImage
  •  

在併發編程中,錯誤處理可能難以正確運行。有時候,我們花了很多時間思考我們的各種流程將如何共享信息和協調,卻忘記考慮如何優雅地處理錯誤。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) // 輸出每個網址的回應狀態
    }
}


上一篇
13.Or-channel
下一篇
15.Pipeline
系列文
Concurrency in go 讀書心得30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言