【问题标题】:How do I handle errors in a worker pool using WaitGroup?如何使用 WaitGroup 处理工作程序池中的错误?
【发布时间】:2014-09-03 03:25:53
【问题描述】:

同时使用 sync.WaitGroupselect 时遇到问题。如果您查看以下 http 请求池,您会注意到如果发生错误,它将永远不会被报告,因为wg.Done() 将阻塞并且不再从通道读取。

package pool

import (
    "fmt"
    "log"
    "net/http"
    "sync"
)

var (
    MaxPoolQueue  = 100
    MaxPoolWorker = 10
)

type Pool struct {
    wg *sync.WaitGroup

    queue  chan *http.Request
    errors chan error
}

func NewPool() *Pool {
    return &Pool{
        wg: &sync.WaitGroup{},

        queue:  make(chan *http.Request, MaxPoolQueue),
        errors: make(chan error),
    }
}

func (p *Pool) Add(r *http.Request) {
    p.wg.Add(1)

    p.queue <- r
}

func (p *Pool) Run() error {
    for i := 0; i < MaxPoolWorker; i++ {
        go p.doWork()
    }

    select {
    case err := <-p.errors:
        return err
    default:
        p.wg.Wait()
    }

    return nil
}

func (p *Pool) doWork() {
    for r := range p.queue {
        fmt.Printf("Request to %s\n", r.Host)

        p.wg.Done()

        _, err := http.DefaultClient.Do(r)

        if err != nil {
            log.Fatal(err)

            p.errors <- err
        } else {
            fmt.Printf("no error\n")
        }
    }
}

来源可以找到here

我怎样才能在使用 WaitGroup 的同时从 go 例程中得到错误?

【问题讨论】:

标签: go wait channel coroutine


【解决方案1】:

我在写这个问题时自己得到了答案,因为我认为这是一个有趣的案例,所以我想与你分享。

同时使用sync.WaitGroupchan 的诀窍是我们包装:

select {
    case err := <-p.errors:
        return err
    default:
        p.wg.Done()
}

一起在for 循环中:

for {
    select {
        case err := <-p.errors:
            return err
        default:
            p.wg.Done()
    }
}

在这种情况下,select 将始终检查错误并等待如果没有任何反应:)

【讨论】:

  • 常见 - 他们让您有机会回答自己的问题,然后您需要等待两天...
  • 我想指出,Go 1.3 已经包含了sync.Pool
  • @OneOfOne 我使用 sync.Pool 来存储响应。但我认为在池中存储一个带有请求、响应和错误的匿名结构是有意义的。让生活更轻松:)
【解决方案2】:

它看起来有点像Tomb library (Tomb V2 GoDoc) 启用的快速失败机制:

tomb 包处理干净的 goroutine 跟踪和终止。

如果任何被跟踪的 goroutine 返回非 nil 错误,或者系统中的任何 goroutine(是否跟踪)调用了 KillKillf 方法,则设置墓穴 Err,@987654327 @ 设置为 falseDying 通道关闭以标记所有被跟踪的 goroutine 都应该自愿终止。

一旦所有被跟踪的 goroutine 终止,Dead 通道将关闭,Wait 解除阻塞并通过结果或显式 KillKillf 方法调用返回第一个非零错误呈现给 tomb,如果没有错误,则为 nil。

你可以看一个例子in this playground:

(摘录)

// start runs all the given functions concurrently
// until either they all complete or one returns an
// error, in which case it returns that error.
//
// The functions are passed a channel which will be closed
// when the function should stop.
func start(funcs []func(stop <-chan struct{}) error) error {
    var tomb tomb.Tomb
    var wg sync.WaitGroup
    allDone := make(chan struct{})
    // Start all the functions.
    for _, f := range funcs {
        f := f
        wg.Add(1)
        go func() {
            defer wg.Done()
            if err := f(tomb.Dying()); err != nil {
                tomb.Kill(err)
            }
        }()
    }
    // Start a goroutine to wait for them all to finish.
    go func() {
        wg.Wait()
        close(allDone)
    }()

    // Wait for them all to finish, or one to fail
    select {
    case <-allDone:
    case <-tomb.Dying():
    }
    tomb.Done()
    return tomb.Err()
}

【讨论】:

    【解决方案3】:

    一个更简单的实现如下所示。 (签入play.golang:https://play.golang.org/p/TYxxsDRt5Wu

    package main
    
    import "fmt"
    import "sync"
    import "time"
    
    type Error struct {
        message string
    }
    
    func (e Error) Error() string {
        return e.message
    }
    
    func main() {
        var wg sync.WaitGroup
        waitGroupLength := 8
        errChannel := make(chan error, 1)
    
        // Setup waitgroup to match the number of go routines we'll launch off
        wg.Add(waitGroupLength)
        finished := make(chan bool, 1) // this along with wg.Wait() are why the error handling works and doesn't deadlock
    
        for i := 0; i < waitGroupLength; i++ {
    
            go func(i int) {
                fmt.Printf("Go routine %d executed\n", i+1)
                time.Sleep(time.Duration(waitGroupLength - i))
                time.Sleep(0) // only here so the time import is needed
                
                if i%4 == 1 {
                    errChannel <- Error{fmt.Sprintf("Errored on routine %d", i+1)}
                }
                
                // Mark the wait group as Done so it does not hang
                wg.Done()
            }(i)
        }
    
        go func() {
            wg.Wait()
            close(finished)
        }()
    
        L:
            for {
                select {
                case <-finished:
                    break L // this will break from loop
                case err := <-errChannel:
                    if err != nil {
                        fmt.Println("error ", err)
                        // handle your error
                    }
                }
            }
    
        fmt.Println("Executed all go routines")
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-16
      • 2020-09-15
      • 1970-01-01
      • 2014-03-17
      • 1970-01-01
      • 1970-01-01
      • 2016-08-18
      • 1970-01-01
      相关资源
      最近更新 更多