<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
官方推薦golang中錯誤處理當做值處理, 既然是值那就可以在channel中傳輸,本文帶你看看golang中channel+error來做非同步錯誤處理有多香,看完本文還會覺得golang的錯誤處理相比java try catch一點優勢都沒有嗎?
如下,一次任務起多個協程非同步處理任務,比如同時做服務/redis/mysql/kafka初始化,當某一個協程出現錯誤(初始化失敗)時,程式是停止還是繼續呢?如何記錄錯誤?如何控制優雅的退出全部工作協程呢?
為了解決類似的問題,常見如下三種解決方案:
如下,最簡單粗暴的方式就是,一旦協程中發生錯誤,記錄紀錄檔立即退出,外層如果想取到錯誤可以通過共用全域性變數,單個協程無法控制所有協程動作。
// 出錯中斷協程,列印紀錄檔,退出 func TestSimpleExit(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() //do something if err := doSomething(); err != nil { t.Logf("Error when call doSomething:%vn", err) return } }() wg.Wait() }
前面講過,既然error是值,那就可以在channel中傳輸,可以單獨開一個channel,所有協程錯誤都傳送到這個通道。
// 資料處理流程 dataFunc := func(ctx context.Context, dataChan chan int, errChan chan error) { defer wg.Done() for { select { case v, ok := <-dataChan: if !ok { log.Println("Receive data channel close msg!") return } if err := doSomething2(v); err != nil { errChan <- err continue } // do ... case <-ctx.Done(): log.Println("Receive exit msg!") return } } } wg.Add(1) go dataFunc(ctx, dataChan, errChan) wg.Add(1) go dataFunc(ctx, dataChan, errChan)
監控錯誤error通道,統一記錄和退出,一旦檢測到錯誤可以通過ctx通知所有協程退出,這裡可以靈活控制監控到錯誤時的錯誤處理策略(是否記錄紀錄檔/是否退出等),error通道可以同步或非同步處理。
整體流程如下
// 錯誤處理流程,error處理通道非同步等待 wg.Add(1) go func(errChan chan error) { defer wg.Done() for { select { case v, ok := <-errChan: if !ok { log.Println("Receice err channel close msg!") return } // 收到錯誤時,可選擇記錄紀錄檔或退出 if v != nil { t.Logf("Error when call doSomething:%vn", v) cancel() // 通知全部退出 return } case <-ctx.Done(): log.Println("Receive exit msg!") return } } }(errChan) dataChan <- 1 wg.Wait()
// 錯誤處理流程,error處理通道同步等待 for { select { case v, ok := <-errChan: if !ok { log.Println("Receice err channel close msg!") goto EXIT } // 收到錯誤時,可選擇記錄紀錄檔或退出 if v != nil { t.Logf("Error when call doSomething:%vn", v) cancel() goto EXIT } case <-ctx.Done(): log.Println("Receive exit msg!") goto EXIT } } EXIT: wg.Wait()
考慮到error輸出到通道後統一處理是golang常用手段,官方也針對封裝了一個error處理包,errgroup顧名思義,多個協程的error被當做一個組,一旦某個協程出錯所有協程都退出,只輸出第一個error。
func TestSimpleChannel5(t *testing.T) { eg, ctx := errgroup.WithContext(context.Background()) dataChan := make(chan int) defer close(dataChan) // 資料處理流程 dataFunc := func() error { for { select { case v, ok := <-dataChan: if !ok { log.Println("Receive data channel close msg!") return nil } if err := doSomething2(v); err != nil { return err } // do ... // 增加ctx通知完成 case <-ctx.Done(): log.Println("Receive exit msg!") return nil } } } eg.Go(dataFunc) eg.Go(dataFunc) eg.Go(dataFunc) dataChan <- 1 // 錯誤處理流程,任何一個協程出現error,則會呼叫ctx對應cancel函數,所有相關協程都會退出 if err := eg.Wait(); err != nil { fmt.Printf("Something is wrong->%vn", err) } }
類似上一小節,可以看到errgroup就是結合waitgroup cancel和channel通道封裝的。
同樣是上述場景,有時候我們的需求是返回所有的錯誤(不是第一個錯誤)。
這種需求,一般考慮使用多錯誤管理(hashicorp/go-multierror庫),如下一個簡答同步任務演示多錯誤管理,所有返回的錯誤可以通過Append歸併成一個錯誤,實際上是通過error wrap的方式合併起來的,因此也可以使用Is/As判斷巢狀error。
// 多路協程error合併,用於多路check場景 func TestSimpleChannel3(t *testing.T) { // 同步執行多個任務,返回error合併 var err = func() error { var result error if err := doSomething(); err != nil { result = multierror.Append(result, err) } if err := doSomething2(nil); err != nil { result = multierror.Append(result, err) } return result }() // 列印輸出 if err != nil { fmt.Printf("%vn", err) } // 獲取錯誤列表 if err != nil { if merr, ok := err.(*multierror.Error); ok { fmt.Printf("%vn", merr.Errors) } } // 判斷是否為某種型別 if err != nil && errors.Is(err, Error1) { fmt.Println("Errors contain error 1") } // 判斷是否其中一個error能夠轉換成指定error var e MyError if err != nil && errors.As(err, &e) { fmt.Println("One Error can be convert to nyerror") } }
那麼,在起多個非同步任務時,就可以如下處理,返回的多個error通過channel消費合併展示。
func TestSimpleChannel4(t *testing.T) { wg := sync.WaitGroup{} taskNum := 10 errChan := make(chan error, taskNum) // 非同步執行多個任務 step := func(stepNum int, errChan chan error) { defer wg.Done() errChan <- fmt.Errorf("step %d error", stepNum) } for i := 0; i < taskNum; i++ { wg.Add(1) go step(i, errChan) } // 等待任務完成 go func() { wg.Wait() close(errChan) }() // err通道阻塞等待,可能的所有錯誤合併 var result *multierror.Error for err := range errChan { result = multierror.Append(result, err) } // 出現一個錯誤時,選擇記錄紀錄檔或退出 if len(result.Errors) != 0 { log.Println(result.Errors) } }
演示程式碼 https://gitee.com/wenzhou1219/go-in-prod/tree/master/error_group
errgroup原始碼解析 https://zhuanlan.zhihu.com/p/416054707
errgroup使用參考 https://zhuanlan.zhihu.com/p/338999914
go-multierror使用參考 https://zhuanlan.zhihu.com/p/581030231
到此這篇關於golang 錯誤處理channel+error真的香的文章就介紹到這了,更多相關golang 錯誤處理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45