从 goroutines 中获取结果的最佳方法

Best approach to getting results out of goroutines

我有两个功能无法更改(请参阅下面的 first()second())。他们正在返回一些数据和错误(输出数据不同,但在下面的示例中,为简单起见,我使用 (string, error)

我想 运行 它们在单独的 goroutine 中 - 我的方法:

package main

import (
    "fmt"
    "os"
)

func first(name string) (string, error) {
    if name == "" {
        return "", fmt.Errorf("empty name is not allowed")
    }
    fmt.Println("processing first")
    return fmt.Sprintf("First hello %s", name), nil
}

func second(name string) (string, error) {
    if name == "" {
        return "", fmt.Errorf("empty name is not allowed")
    }
    fmt.Println("processing second")
    return fmt.Sprintf("Second hello %s", name), nil
}

func main() {
    firstCh := make(chan string)
    secondCh := make(chan string)
    
    go func() {
        defer close(firstCh)
        res, err := first("one")
        if err != nil {
            fmt.Printf("Failed to run first: %v\n", err)
        }
        firstCh <- res
    }()

    go func() {
        defer close(secondCh)
        res, err := second("two")
        if err != nil {
            fmt.Printf("Failed to run second: %v\n", err)
        }
        secondCh <- res
    }()

    resultsOne := <-firstCh
    resultsTwo := <-secondCh

    // It's important for my app to do error checking and stop if errors exist.
    if resultsOne == "" || resultsTwo == "" {
        fmt.Println("There was an ERROR")
        os.Exit(1)
    }

    fmt.Println("ONE:", resultsOne)
    fmt.Println("TWO:", resultsTwo)
}

我相信一个警告是 resultsOne := <- firstCh 阻塞直到第一个 goroutine 完成,但我不太关心这个

能否请您确认我的方法是好的?还有哪些其他方法更适合我的情况?

这个例子看起来很不错。一些改进是:

  • 声明您的频道已缓冲
   firstCh := make(chan string, 1)
   secondCh := make(chan string, 1)

对于无缓冲通道,发送操作阻塞(直到有人接收)。如果你的 goroutine #2 比第一个快得多,它也必须等到第一个完成,因为你按顺序收到:

    resultsOne := <-firstCh // waiting on this one first
    resultsTwo := <-secondCh // sender blocked because the main thread hasn't reached this point
  • 使用"golang.org/x/sync/errgroup".Group。该程序会让人感觉“不太原生”,但它使您无需手动管理频道——在非人为设置中进行交易,以同步写入结果:
func main() {
    var (
        resultsOne string
        resultsTwo string
    )

    g := errgroup.Group{}
    
    g.Go(func() error {
        res, err := first("one")
        if err != nil {
            return err
        }
        resultsOne = res
        return nil
    })

    g.Go(func() error {
        res, err := second("two")
        if err != nil {
            return err
        }
        resultsTwo = res
        return nil
    })

    err := g.Wait()
    // ... handle err