为WaitGroup.Wait()分配超时的惯用方法是什么?
我想这样做的原因是为了保护我的“日程安排人员”免于永远等待一个错误的“工人”。这导致了一些哲学问题(即一旦有错误的工人,系统如何可靠地继续?),但我认为这个问题超出了这个范围。
我有一个答案,我会提供。现在我已经把它写下来了,它看起来并不那么糟糕,但它仍然感觉比它应该更复杂。我想知道是否有更简单,更惯用的东西,甚至是不使用WaitGroups的替代方法。
的Ta。
答案 0 :(得分:29)
大多数情况下,您发布的below解决方案尽可能好。一些改进它的技巧:
defer
语句来表示完成,即使函数突然终止也会执行它。WaitGroup
,只需在作业完成时发送一个值或关闭频道(您在select
声明中使用的频道相同)。timeout := time.Second
。例如,指定2秒为:timeout := 2 * time.Second
。您不需要转换,time.Second
已经是time.Duration
类型,将其与类似2
的无类型常量相乘也会产生time.Duration
类型的值。 我还会创建一个包含此功能的帮助器/实用程序功能。请注意,WaitGroup
必须作为指针传递,否则副本将不会被通知" WaitGroup.Done()
次来电。类似的东西:
// waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out.
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
c := make(chan struct{})
go func() {
defer close(c)
wg.Wait()
}()
select {
case <-c:
return false // completed normally
case <-time.After(timeout):
return true // timed out
}
}
使用它:
if waitTimeout(&wg, time.Second) {
fmt.Println("Timed out waiting for wait group")
} else {
fmt.Println("Wait group finished")
}
在Go Playground上尝试。
答案 1 :(得分:4)
我是这样做的:http://play.golang.org/p/eWv0fRlLEC
go func() {
wg.Wait()
c <- struct{}{}
}()
timeout := time.Duration(1) * time.Second
fmt.Printf("Wait for waitgroup (up to %s)\n", timeout)
select {
case <-c:
fmt.Printf("Wait group finished\n")
case <-time.After(timeout):
fmt.Printf("Timed out waiting for wait group\n")
}
fmt.Printf("Free at last\n")
它工作正常,但是最好的方法吗?
答案 2 :(得分:2)
大多数现有答案表明泄漏了goroutine。向WaitGroup.Wait分配超时的惯用方式是使用基础的sync/atomic包原语。我从@icza答案中获取了代码,并使用atomic
包重写了代码,并添加了上下文取消功能,因为这是通知超时的惯用方式。
package main
import (
"context"
"fmt"
"sync/atomic"
"time"
)
func main() {
var submitCount int32
// run this instead of wg.Add(1)
atomic.AddInt32(&submitCount, 1)
// run this instead of wg.Done()
// atomic.AddInt32(&submitCount, -1)
timeout := time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
fmt.Printf("Wait for waitgroup (up to %s)\n", timeout)
waitWithCtx(ctx, &submitCount)
fmt.Println("Free at last")
}
// waitWithCtx returns when passed counter drops to zero
// or when context is cancelled
func waitWithCtx(ctx context.Context, counter *int32) {
ticker := time.NewTicker(10 * time.Millisecond)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if atomic.LoadInt32(counter) == 0 {
return
}
}
}
}
答案 3 :(得分:0)
我写了一个封装了并发逻辑https://github.com/shomali11/parallelizer的库,你也可以通过它来传递超时。
这是一个没有超时的例子:
func main() {
group := parallelizer.DefaultGroup()
group.Add(func() {
for char := 'a'; char < 'a'+3; char++ {
fmt.Printf("%c ", char)
}
})
group.Add(func() {
for number := 1; number < 4; number++ {
fmt.Printf("%d ", number)
}
})
err := group.Run()
fmt.Println()
fmt.Println("Done")
fmt.Printf("Error: %v", err)
}
输出:
a 1 b 2 c 3
Done
Error: <nil>
以下是超时的示例:
func main() {
options := ¶llelizer.Options{Timeout: time.Second}
group := parallelizer.NewGroup(options)
group.Add(func() {
time.Sleep(time.Minute)
for char := 'a'; char < 'a'+3; char++ {
fmt.Printf("%c ", char)
}
})
group.Add(func() {
time.Sleep(time.Minute)
for number := 1; number < 4; number++ {
fmt.Printf("%d ", number)
}
})
err := group.Run()
fmt.Println()
fmt.Println("Done")
fmt.Printf("Error: %v", err)
}
输出:
Done
Error: timeout
答案 4 :(得分:0)
这不是这个问题的实际答案,但是当我遇到这个问题时,解决了我的小问题(更简单)。
我的'工人'正在做http.Get()请求所以我只是在http客户端设置超时。
urls := []string{"http://1.jpg", "http://2.jpg"}
wg := &sync.WaitGroup{}
for _, url := range urls {
wg.Add(1)
go func(url string) {
client := http.Client{
Timeout: time.Duration(3 * time.Second), // only want very fast responses
}
resp, err := client.Get(url)
//... check for errors
//... do something with the image when there are no errors
//...
wg.Done()
}(url)
}
wg.Wait()
答案 5 :(得分:0)
这是一个坏主意。 请勿放弃goroutines ,否则可能会导致竞争,资源泄漏和意外情况,最终影响应用程序的稳定性。
请在整个代码中始终使用超时,以确保不会永远阻塞goroutine或花费太长时间来运行该程序。
实现这一目标的惯用方式是通过context.WithTimeout()
:
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
// Now perform any I/O using the given ctx:
go func() {
err = example.Connect(ctx)
if err != nil { /* handle err and exit goroutine */ }
. . .
}()
现在,您可以放心使用WaitGroup.Wait()
,因为它将始终可以及时完成。
答案 6 :(得分:0)
另一种不泄漏 wg.Wait()
例程的解决方案:只需使用(得到良好支持和广泛使用)golang.org/x/sync/semaphore
:
sync.WaitGroup{}
代替sem.NewWeighted(N)
(您必须提前知道N
)wg.Add(1)
代替 err := sem.Acquire(ctx, 1)
defer wg.Done()
代替 defer sem.Release(1)
wg.Wait()
与带超时的上下文一起使用,而不是 sem.Acquire(ctx, N)
。sync.WaitGroup
(当您只调用 Add(1)
和 Release(1)
N
次时)。仔细阅读文档。package main
import (
"context"
"log"
"time"
"golang.org/x/sync/semaphore"
)
func worker(n int) {
time.Sleep(time.Duration(n) * time.Second)
log.Printf("Worker %v finished", n)
}
func main() {
const N = 5
sem := semaphore.NewWeighted(N)
for i := 0; i < N; i++ {
err := sem.Acquire(context.Background(), 1)
if err != nil {
log.Fatal("sem.Acquire err", err)
}
go func(n int) {
defer sem.Release(1)
worker(n)
}(i)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
err := sem.Acquire(ctx, N)
if err != nil {
log.Println("sem.Acquire err:", err)
return
}
log.Println("sem.Acquire ok")
}
结果:
2009/11/10 23:00:00 Worker 0 finished
2009/11/10 23:00:01 Worker 1 finished
2009/11/10 23:00:02 Worker 2 finished
2009/11/10 23:00:02 sem.Acquire err: context deadline exceeded
答案 7 :(得分:0)
提出一个不会泄漏 goroutine 或依赖轮询(睡眠)的解决方案:
const increaseEndDateFinish = (e, idx) => {
const target = e.target;
target.style.width = '200px';
console.log(target.clientWidth);
};
用法:
import "atomic"
type WaitGroup struct {
count int32
done chan struct{}
}
func NewWaitGroup() *WaitGroup {
return &WaitGroup{
done: make(chan struct{}),
}
}
func (wg *WaitGroup) Add(i int32) {
select {
case <-wg.done:
panic("use of an already closed WaitGroup")
default:
}
atomic.AddInt32(&wg.count, i)
}
func (wg *WaitGroup) Done() {
i := atomic.AddInt32(&wg.count, -1)
if i == 0 {
close(wg.done)
}
if i < 0 {
panic("too many Done() calls")
}
}
func (wg *WaitGroup) C() <-chan struct{} {
return wg.done
}
答案 8 :(得分:0)
我们对我们的一个系统也有同样的需求。通过将上下文传递给 goroutines 并在我们面临超时时关闭该上下文,我们将防止 goroutine 泄漏。
func main() {
ctx := context.Background()
ctxWithCancel, cancelFunc := context.WithCancel(ctx)
var wg sync.WaitGroup
Provide(ctxWithCancel, 5, &wg)
Provide(ctxWithCancel, 5, &wg)
c := make(chan struct{})
go func() {
wg.Wait()
c <- struct{}{}
fmt.Println("closed")
}()
select {
case <-c:
case <-time.After(20 * time.Millisecond):
cancelFunc()
fmt.Println("timeout")
}
}
func Work(ctx context.Context, to int) {
for i := 0; i < to; i++ {
select {
case <-ctx.Done():
return
default:
fmt.Println(i)
time.Sleep(10 * time.Millisecond)
}
}
}
func Provide(ctx context.Context, to int, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
Work(ctx, to)
wg.Done()
}()
}