代码之家  ›  专栏  ›  技术社区  ›  pandolf

一旦一个例程遇到错误,立即报告错误

  •  0
  • pandolf  · 技术社区  · 2 年前

    我正试图找出一个例程一旦遇到错误就报告错误的基本模式。这是我正在使用的代码的基本逻辑

    func doSomeWork(num int, errChan chan <- error) {
            if num >= 10 {
                    errChan <- errors.New("Greater than 10")
            }
            return
    }
    
    const numWorkers int = 25
    
    func run() error {
            var wg sync.WaitGroup
            errChan := make(chan error)
            for i := 0; i < numWorkers; i++ {
                    wg.Add(1)
                    go func(num int) {
                            defer wg.Done()
                            doSomeWork(num, errChan)
                    }(i)
            }
            go func() {
                    wg.Wait()
                    close(errChan)
            }()
            err, ok := <-errChan
            if !ok {
                    return nil
            }
            return  err
    }
    

    这按预期工作,当numWorkers<10,当numworker为>=时出错10.我其实并不关心工人的数量,这只是一个简单的例子。然而,这感觉不太习惯,当一个例程失败时,可能会有一些例程泄漏,因为其他例程仍在运行,而等待所有例程的例程也仍在运行。有没有一种更惯用的方法可以做到这一点,即一旦一个例程失败,就停止所有例程?

    1 回复  |  直到 2 年前
        1
  •  1
  •   Claudine Gay    2 年前

    代码很接近,但当发送多个错误时会泄漏goroutines。第一个发送的工人 errChan 退出,因为主goroutine接收到该值。发送到的所有其他工作人员 errChan 永远阻止,因为没有goroutine来接收值。这个 wg.Wait(); close(errChan) goroutine也永远阻止等待工人完成。

    修复有两个方面:使用非阻塞发送到通道并确保 errChan 可以接收至少一个值。

    有关更多详细信息,请参阅下面的评论:

    func run() error {
        var wg sync.WaitGroup
        // Create channel with capacity one to ensure that
        // a goroutine can send to the channel before this
        // goroutine is ready to receive.
        errChan := make(chan error, 1)  // <-- note 1 here
    
        for i := 0; i < numWorkers; i++ {
            wg.Add(1)
            go func(num int) {
                defer wg.Done()
                doSomeWork(num, errChan)
            }(i)
        }
        go func() {
            wg.Wait()
            // All goroutines are done, but the main goroutine
            // will be waiting if there were no errors.  Close 
            // the channel so that the main goroutine receives
            // a zero error value.
            close(errChan)
        }()
        // The if statement in the original is not needed because
        // the zero value of the error interface is nil. 
        return <-errChan
    }
    
    
    func doSomeWork(num int, errChan chan<- error) {
        if num >= 10 {
            select {
            case errChan <- errors.New("Greater than 10"):
                 // Successful send on channel
            default:
                 // Channel is not ready. This implies that
                 // at least one other goroutine sent an error
                 // to the channel.  There's nothing for us to
                 // do here. error.
            }
        }
    }
    

    Run the program on the Playground !