如何使用WaitGroup处理工作池中的错误?

时间:2014-07-12 17:53:33

标签: go wait channel coroutine

我在使用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例程中获取错误?

3 个答案:

答案 0 :(得分:5)

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

使用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将始终检查错误并等待,如果没有任何反应:)

答案 1 :(得分:3)

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

  

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

     

如果任何跟踪的goroutines返回非零错误,或系统中的任何goroutine(跟踪或未跟踪)调用KillKillf方法,则{{1} }已设置,Err设置为Alivefalse频道已关闭,以标记所有跟踪的goroutine应尽快自愿终止。

     

所有跟踪的goroutine终止后,Dying频道关闭,Dead取消阻止,并通过结果或显式Wait或者返回提交给坟墓的第一个非零错误Kill方法调用,如果没有错误,则为nil。

您可以看到示例in this playground

(提取物)

Killf

答案 2 :(得分:0)

一个更简单的实现如下所示。 (检查 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")
}