【问题标题】:How to run functions concurrently in a Go API instead of sequential? [closed]如何在 Go API 中同时运行函数而不是顺序运行? [关闭]
【发布时间】:2021-04-10 07:25:07
【问题描述】:

在一个API中,如果我们需要同时查询多个表,如何实现并发而不是按照顺序的方式, 即

func sampleAPI(w http.ResponseWriter, r *http.Request) {
    a, err := getFromATable();  //1
    if err != nil {
       w.WriteHeader(http.StatusInternalServerError)
       return
    }
    b, err := getFromBTable();  //2
    if err != nil {
       w.WriteHeader(http.StatusInternalServerError)
       return
    }
    c, err := getFromCTable();  //3
    if err != nil {
       w.WriteHeader(http.StatusInternalServerError)
       return
    }
    .
    .
    .
}

我想同时调用上面的函数 1,2,3。我怎样才能做到这一点

【问题讨论】:

  • "我想同时调用上面的函数 1,2,3 怎么实现" -- 使用go关键字。见tour.golang.org/concurrency/1
  • @mkopriva 我试过但有一些相关的问题。
  • 在这种情况下,您应该包含您尝试过的代码和遇到的问题,您应该解释为什么只使用 go 关键字是不够的。您还应该包括sampleAPI 的期望输出应该考虑到它的依赖项是同时执行的事实。对于我们没有完整定义的问题,很难提供适当的解决方案。
  • @mkopriva 下次我会尝试写更详细的问题,以便人们理解和回答。虽然目前,在浏览了解决方案可用的选项(Go Routine、WaitGroup、ErrGroup)之后,ErrGroup 似乎更可靠,所以我同意了。

标签: postgresql go concurrency


【解决方案1】:

使用错误通道进行同步

func sampleAPI(w http.ResponseWriter, r *http.Request) {
    chErr := make(chan error)

    var a correctType
    go func() {
        var err error
        a, err = getFromATable()
        chErr <- err
    }()

    var b correctType
    go func() {
        var err error
        b, err = getFromBTable()
        chErr <- err
    }()

    var c correctType
    go func() {
        var err error
        c, err = getFromCTable()
        chErr <- err
    }()

    var err error
    for i := 0; i < 3; i++ {
        if r := <-chErr; r != nil {
            err = r
        }
    }
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        // etc.
        return
    }

    // continue to do stuff with a, b, c
}

一些注意事项:

  • 每个 go 函数都必须向 chErr 添加一个值。确保它!如果没有错误,请将nil 写入chErr(如果没有错误,此示例将执行此操作)。
  • for 循环的迭代次数必须与启动的 go 函数相同。
  • for 循环在继续之前确保所有函数都已完成(有或无错误)。
  • 使用错误进行同步很方便,因为它对于所有函数都是相同的类型。返回类型可能不同。如果我们需要在错误时取消,无论如何我们都需要从 goroutines 中恢复错误。

使用errgroup

正如@Зелёный 在 cmets 中所建议的,这里是一个使用(仍然)实验包 errgroup 的示例:

func sampleAPI(w http.ResponseWriter, r *http.Request) {
    g, ctx := errgroup.WithContext(context.TODO())

    var a correctType
    g.Go(func() (err error) {
        a, err = getFromATable(ctx)
        return err
    })

    var b correctType
    g.Go(func() (err error) {
        b, err = getFromBTable(ctx)
        return err
    })

    var c correctType
    g.Go(func() (err error) {
        c, err = getFromCTable(ctx)
        return err
    })

    if err := g.Wait(); err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        // etc.
        return
    }

    // continue to do stuff with a, b, c
}

一些注意事项:

  • 这个会检查所有错误并为您返回第一个错误。
  • 如果出现错误,它还会取消剩余的调用(因此是 ctx
  • 它使用sync.WaitGroup
  • 缺点:它是一个额外的依赖项,因为它还不是标准库的一部分。

使用WaitGroup

您也可以使用sync.WaitGroup 等待所有函数返回结果。

func sampleAPI(w http.ResponseWriter, r *http.Request) {
    var wg sync.WaitGroup
    wg.Add(3)

    var a correctType
    var errA error
    go func() {
        defer wg.Done()
        a, errA = getFromATable()
    }()

    var b correctType
    var errB error
    go func() {
        defer wg.Done()
        b, errB = getFromBTable()
    }()

    var c correctType
    var errC error
    go func() {
        defer wg.Done()
        c, errC = getFromCTable()
    }()

    wg.Wait()
    
    if errA != nil {
        w.WriteHeader(http.StatusInternalServerError)
        // etc.
        return
    }
    if errB != nil {
        w.WriteHeader(http.StatusInternalServerError)
        // etc.
        return
    }
    if errC != nil {
        w.WriteHeader(http.StatusInternalServerError)
        // etc.
        return
    }

    // continue to do stuff with a, b, c
}

一些注意事项:

  • 这里需要 3 个错误变量。
  • 你需要检查wg.Wait之后的所有3个错误变量,这有点冗长。

【讨论】:

  • 这有一个 goroutine 泄漏:如果遇到错误,它会立即返回,并且不会从 chErr 读取其余值。由于通道是无缓冲的,goroutines 将被阻塞,试图永远发送给它。
  • 好收获。我会解决的。
  • godoc.org/golang.org/x/sync/errgroup 包,比你的代码方便又干净。
  • @Зелёный:谢谢你的提示。不过,无需对有效的解决方案投反对票。另请注意,您所指的包是实验性的,而不是标准库的一部分(还)。
  • 我没有投反对票。虽然它是实验性的,但它比您的代码更健壮且不易出错。
【解决方案2】:

您也可以使用等待组来等待所有 api 调用完成,然后再继续,例如:

func sampleAPI(w http.ResponseWriter, r *http.Request) {
    var res1, res2, res3 myCustomType
    var err1, err2, err2 error

    var wg sync.WaitGroup
        wg.Add(3)

    go func() {
        defer wg.Done()
        res1, err1 = getFromATable();  //1
    }()

    go func() {
        defer wg.Done()
        res2, err2 = getFromBTable();  //2
    }()

    go func() {
        defer wg.Done()
        res3, err3 = getFromXTable();  //3
    }()

    wg.Wait()
}

进一步参考https://gobyexample.com/waitgroupshttps://tutorialedge.net/golang/go-waitgroup-tutorial/

【讨论】:

  • 你不能添加到 goroutine 内的 WaitGroup。在调度 goroutine 之前总是调用Add,就像你引用的例子一样。
  • 否决票可能是由于这个错误?
  • @Matteo 或者由于其他用户已经修复的其他拼写错误/错误。
【解决方案3】:

如果 a、b 和 c 是不同的类型,您可以尝试以下方法。 interface{} 类型只是为了展示如何编码,您可以根据需要修改通道类型。

    aCh := make(chan interface{})
    bCh := make(chan interface{})
    cCh := make(chan interface{})
    defer close(aCh)
    defer close(bCh)
    defer close(cCh)
    
    go func() {
      a, _ := GetFromATable()
      aCh <- a
    }()
    go func() {
      b, _ := GetFromBTable()
      bCh <- b
    }()
    go func() {
      c, _ := GetFromCTable()
      cCh <- c
    }()

    fmt.Println(<- aCh, <- bCh, <- cCh)

如果三个输出都属于同一类型,您可以只需要使用单通道或使用for循环和select语句来处理流需求。

【讨论】:

  • 当还必须检查主函数中的错误时,这变得更加复杂。另请注意,不需要关闭频道。
  • 只需回答问题,因为问题不需要错误处理:)
  • 提问者没有明确要求,对吧。尽管从我收集的代码示例中,他想知道如何返回(和处理)错误。注意:虽然我没有投反对票。永远不要。
猜你喜欢
  • 1970-01-01
  • 2013-08-13
  • 1970-01-01
  • 2010-09-09
  • 1970-01-01
  • 1970-01-01
  • 2020-06-25
  • 1970-01-01
  • 2020-08-16
相关资源
最近更新 更多