Golang WaitGroup超时错误处理

3
在以下代码中,如果启动的goroutine一个执行时间太长(例如> 10秒),我该如何添加适当的超时错误处理?请注意,我不想有一个“总体”超时,而是对每个goroutine进行超时处理,以便在我的错误报告中还可以知道哪个goroutine超时了。
var wg sync.WaitGroup

for _, element:= range elements{
    wg.Add(1)
    go doWork(element, &wg)
}
wg.Wait()

亲切的问候
3个回答

1
一种不错的方法是使用 context.WithDeadline:

// WithDeadline returns a copy of the parent context with the deadline adjusted
// to be no later than d. If the parent's deadline is already earlier than d,
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
// context's Done channel is closed when the deadline expires, when the returned
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond))

    // Even though ctx will be expired, it is good practice to call its
    // cancelation function in any case. Failure to do so may keep the
    // context and its parent alive longer than necessary.
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
    }

}

1
context.WithDeadline调用可以简化为context.WithTimeout(context.Background(), 100*time.Millisecond)。 - Peter
@Peter:WithTimeout 本身调用 WithDeadline,请看内部实现:func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) } - wasmup

1
你可以这样使用上下文
func doWork(ctx context.Context, element Element, wg &sync.WaitGroup) {
    defer wg.Done()

    done := make(chan struct{})
    go func() {
       // do some work on element
       done <- struct{}{} // signal work is done
    }

    select {
       case <- done: 
       { 
          // work completed in time
       }
       case <- ctx.Done:
       {
         // timeout reached
       }
    }
}

contexts := make([]*context.Context, len(elements))

for _, element:= range elements{
    wg.Add(1)
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()
    contexts = append(contexts, ctx)
    go doWork(ctx, element, &wg)
}
wg.Wait()

for i, ctx := range contexts {
  if ctx.Err() {
     fmt.Println("Go routine ", i, "canceled due to", ctx.Err())
  }
}

1

我确实有同样的问题,并想出了以下方法:

https://play.golang.org/p/9F9T_sYIof

使用:context.WithTimeout(context.Background(), 10*time.Second)

https://play.golang.org/p/WK0ebe0c9t

我不知道这是否是正确的做法,但它有效:

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

func doWork(element int, wg *sync.WaitGroup) {
    ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
    defer cancel()

    ch := make(chan struct{})

    go func() {
        time.Sleep(time.Second)
        fmt.Printf("element = %+v\n", element)
        ch <- struct{}{}
    }()

    select {
    case <-ch:
    case <-ctx.Done():
        fmt.Println(ctx.Err())
    }
    wg.Done()
}

func main() {
    var wg sync.WaitGroup

    elements := []int{1, 2, 3}

    for _, element := range elements {
        wg.Add(1)
        go doWork(element, &wg)
    }
    wg.Wait()
}

注意 doWork 函数中的 goroutine:

go func(ch chan struct{}) {
   // your code logic goes here
}(ch)

这是我不确定是否最佳实践的部分,但似乎是在使用上下文时处理 ctx.Done() 时要遵循的模式。


context.WithDeadline调用可以简化为context.WithTimeout(context.Background(), 10*time.Second) - Peter
这段代码会创建内存泄漏,因为它是未缓冲的通道 ch := make(chan struct{}),所以请更改为 ch := make(chan struct{}, 1) 以避免内存泄漏。 - fatih tekin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接