成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go 并發中 panic 的處理

開發 前端
your code 和 go on 誰先執行是不確定的,這取決于調度。如果想等協程執行完再繼續執行的話怎么辦呢?比如下面代碼。

Go 中的并發

在 go 中,可以通過原聲關鍵字 go 創建協程。

go func() {
  // your code
}()
// go on

your code 和 go on 誰先執行是不確定的,這取決于調度。如果想等協程執行完再繼續執行的話怎么辦呢?比如下面代碼。

go func() {
  // your code1
}()
go func() {
  // your code2
}()
// go on

其中一種方式是使用 sync.WaitGroup。

var wg sync.WaitGroup
wg.Add(2)
go func() { 
  wg.Done()
}()
go func() {
 wg.Done()
}()
wg.Wait()
// go on

wg.Wait() 會阻塞直到兩個協程執行完后,這個有點類似多線程中的線程屏障。但是這種方式過于靈活,我們需要控制好 Add 和 Done 的邏輯,否則會導致一直阻塞或者 panic。幸好 go 還提供了 errgroup.Group,代碼如下。

var g errgroup.Group
g.Go(func() error {})
g.Go(func() error {})
g.Wait()

在用法上,errgroup.Group 和 sync.WaitGroup 很像,前者也是基于后者實現的,但是前者使用起來相對簡單、并且還實現了自動管理 Add/Done、限制并發數等能力。在日常開發中也是經常使用 errgroup.Group 來實現并發。

并發中的 panic 問題

接下來看一下使用協程實現并發時的 panic 處理問題,前面兩種都是通過 go 關鍵字創建的協程,所以只能在函數里手動處理,所以主要看一下 errgroup.Group 的 panic 處理問題。我們從 errgroup.Group 的 Go 函數開始看。

func (g *Group) Go(f func() error) {
  if g.sem != nil {
    g.sem <- token{}
  }
  g.add(f)
}

Go 前面的邏輯用于限制并發,主要是 add 函數。

func (g *Group) add(f func() error) {
    g.wg.Add(1)
    go func() {
        defer g.done()
        // panic 處理
        defer func() {
            v := recover()
            g.mu.Lock()
            defer g.mu.Unlock()
            // 記錄 panic 信息,但是只會記錄第一次 panic 的信息
            if v != nil && g.panicValue == nil {
              g.panicValue = ...
            }
        }()
        // 用戶函數
        err := f()
        // 記錄錯誤信息,只記錄第一個錯誤
        if err != nil {
          g.errOnce.Do(func() {
            g.err = err
          })
        }
    }()
}

可以看到 errgroup.Group 處理了 panic 問題并記錄了 panic 信息,看起來我們的函數里可以不處理 panic,那 errgroup.Group 是怎么處理 panic 信息的呢?接著看 Wait 函數中處理。

func (g *Group) Wait() error {
    g.wg.Wait()
    if g.cancel != nil {
        g.cancel(g.err)
    }
    if g.panicValue != nil {
        panic(g.panicValue)
    }
    return g.err
}

可以看到最終會在 Wait 函數執行 panic,所以我們只需要處理 Wait 函數的 panic 就行。

defer func() {
    v := recover()
    // ...
}()
g.Wait()

但是還有有一個問題是 errgroup.Group 只會記錄第一個 panic,如果我們多個協程發生了 panic 則會丟失信息,所以我們最好還是自己處理,代碼如下。

defer func() {
    v := recover()
    // ...
}()


var g errgroup.Group


g.Go(func() error {
  defer func() {
    v := recover()
    // ...
  }()
})
g.Wait()

這樣就可以記錄每一個協程的 panic 信息,那么如果協程里的 defer 中再次發生 panic 怎么辦呢?通過之前的分析可以知道,這個 panic 會被 errgroup.Group 捕獲,并最終在 Wait 中執行 panic,所以即使協程里處理了 panic,我們也需要處理 Wait 的 panic。如果我們運行在一些框架中,框架往往會幫我們處理,比如 kitex 的處理如下。

defer func() {
    // panic 處理
    if handlerErr := recover(); handlerErr != nil {
    err = kerrors.ErrPanic.WithCauseAndStack(
    fmt.Errorf(
    "[happened in biz handler, method=%s.%s, please check the panic at the server side] %s",
    svcInfo.ServiceName, methodName, handlerErr),
    string(debug.Stack()))
    }
}()
    // 執行業務代碼
minfo := svcInfo.MethodInfo(methodName)
implHandlerFunc := minfo.Handler()
err = implHandlerFunc(ctx, svc.handler, args, resp)

無論是協程里處理還是對 Wait 函數的處理,每次都要寫類似的代碼非常麻煩,一旦忘記寫就容易出現 panic,嚴重還會導致進程 crash(如果上層也沒有處理 panic)。我們可以基于 errgroup.Group 提供一個安全版本的 errgroup.Group。

type ErrGroup struct {
    errgroup.Group
    cancel  func(error)
      // 可以自定義處理函數
      handler func(context.Context, *error)
}


// 默認處理
func handler(_ context.Context, err *error) {
    if e := recover(); e != nil {
        if err != nil {
            *err = fmt.Errorf("panic happen: %v", e)
        }
    }
}


func WithContext(ctx context.Context) (*ErrGroup, context.Context) {
    ctx, cancel := context.WithCancelCause(ctx)
    return &ErrGroup{cancel: cancel}, ctx
}


func (e *ErrGroup) SafeGo(ctx context.Context, f func() error) {
    e.Go(func() (err error) {
    if e.handler != nil {
        defer e.handler(ctx, &err)
    } else {
        defer handler(ctx, &err)
    }
    return f()
    })
}


func (e *ErrGroup) SafeWait() (err error) {
    defer func() {
    if e := recover(); e != nil {
            err = fmt.Errorf("panic happen: %v", e)
        }
    }()
    err = e.Wait()
    if e.cancel != nil {
        e.cancel(err)
    }
    return err
}

用法如下。

ctx := context.Background()
var eg safe_errgroup.ErrGroup
eg.SafeGo(ctx, func() error {
  panic("oops")
})
err := eg.Wait()

這樣就可以安全地實現并發了,具體可以參考:

https://github.com/theanarkh/safe_errgroup。

責任編輯:武曉燕 來源: 編程雜技
相關推薦

2024-07-26 08:32:44

panic?Go語言

2025-03-31 00:29:44

2020-12-17 06:25:05

Gopanic 模式

2025-06-09 01:15:00

2025-03-31 08:57:25

Go程序性能

2022-07-19 08:01:55

函數Go格式化

2025-06-10 02:00:00

Golangmap

2020-01-14 11:17:33

Go并發Linux

2023-10-09 07:14:42

panicGo語言

2022-06-09 10:42:47

GoJSON

2021-09-27 23:28:29

Go多協程并發

2021-11-10 15:18:16

JavaGo命令

2023-12-22 14:07:00

Go輕量級Goroutines

2023-12-27 08:03:53

Go優化代碼

2023-12-26 22:05:53

并發代碼goroutines

2025-02-06 13:19:31

RustPin系統

2021-07-28 08:32:58

Go并發Select

2023-12-21 07:09:32

Go語言任務

2023-12-29 08:10:41

Go并發開發

2021-07-30 07:28:15

WorkerPoolGo語言
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久av在线播放 | 免费a网| 欧美成人一区二区三区 | 网色| 久久精品日产第一区二区三区 | 人人鲁人人莫人人爱精品 | 视频在线观看一区 | 精品1区2区3区4区 | 天天综合永久 | 成人免费视频网站在线观看 | 国产欧美性成人精品午夜 | k8久久久一区二区三区 | 亚洲视频免费在线播放 | 在线视频a | 久久青 | 欧美自拍网站 | 国产99久久精品一区二区永久免费 | 啪啪综合网 | 二区亚洲 | 亚洲中午字幕 | 国产高清免费视频 | 亚洲三级在线 | 国产做a爱片久久毛片 | 日韩免费激情视频 | 国产久视频 | 日韩字幕 | 欧美精品一区二区免费视频 | 亚洲成人免费av | 欧美日韩精品中文字幕 | 成人影院在线 | 一区二区三区四区不卡视频 | 日韩欧美在线观看 | 久久高清亚洲 | 色综合国产 | 国产欧美一区二区久久性色99 | 国产精品久久久久久亚洲调教 | 韩日在线| 国产一区二 | 成人国产在线观看 | 国产一区二区三区四区五区加勒比 | 国产精品日女人 |