我在使用sync.WaitGroup
和select
时遇到了问题。如果您查看以下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例程中获取错误?
答案 0 :(得分:5)
在我写这个问题时,我得到了自己的答案,我认为这是一个有趣的案例,我想与你分享。
使用sync.WaitGroup
和chan
的技巧是我们换行:
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 library(Tomb V2 GoDoc)启用的快速失败机制:
Tomb包处理干净的goroutine跟踪和终止。
如果任何跟踪的goroutines返回非零错误,或系统中的任何goroutine(跟踪或未跟踪)调用
Kill
或Killf
方法,则{{1} }已设置,Err
设置为Alive
,false
频道已关闭,以标记所有跟踪的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")
}