Golang发出并发请求并合并响应

时间:2018-09-25 12:48:25

标签: go concurrency

我编写了go代码来独立调用多个http请求并合并结果。

有时组合方法中缺少值。

func profile(req *http.Request)  (UserMe, error, UserRating, error) {

    wgcall := &sync.WaitGroup{}

    uChan := make(chan ResUser)
    rChan := make(chan ResRating)

        // variable inits
    var meResp UserMe
    var ratingResp UserRating

    go func() {
        res := <-uChan
                meResp = res.Value
    }()

    go func() {
        res := <-rChan
                ratingResp = res.Value
    }()

    wgcall.Add(2)
    go me(req, wgcall, uChan)
    go rate(req, wgcall, rChan)

    wgcall.Wait()

    logrus.Info(meResp)  // sometimes missing
    logrus.Info(ratingResp) // sometimes missing

    return meResp, meErr, ratingResp, ratingErr
}

但是me和rating调用会按预期从api请求中返回值。

func me(req *http.Request, wg *sync.WaitGroup, ch chan ResUser) {
    defer wg.Done()

    // http call return value correclty
    me := ...
    ch <- ResUser{
        Value := // value from rest
    }
    logrus.Info(fmt.Sprintf("User calls  %v" , me))  // always return the values
    close(ch)
}

func rate(req *http.Request, wg *sync.WaitGroup, ch chan ResRating) {
    defer wg.Done()

        // make http call
    rating := ...
    ch <- ResRating{
        Value := // value from rest
    }
    logrus.Info(fmt.Sprintf("Ratings calls %v" , rating)) // always return the values

    close(ch)
}

问题是:配置文件函数的meResp和ratingResp不能始终获取值。有时只有meResp或ratingResp,有时两者都符合预期。

但是我和rate函数总是调用获取值。

能帮我解决这个问题吗?

1 个答案:

答案 0 :(得分:3)

您的代码中有一个race condition

没有任何障碍可以确保从profileuChan读取的rChan方法中的goroutine已填充变量meRespratingResp 之前,您将从profile返回。

通过在profile中删除通道和内联声明的goroutine的使用,可以极大地简化代码。相反,只需直接填充响应值即可。在这种情况下,使用通道或goroutine读取它们不会带来任何好处,因为您只打算发送一个值,并且要求在返回之前必须同时存在两个HTTP调用产生的值。

您可以通过修改merate的签名以接收指向要写入其输出的位置的指针,或者通过使用接收其输出值的小函数包装它们的调用来实现此目的填充profile中的值。重要的是,WaitGroup应该仅在填充值后的 信号通知:

wgcall := &sync.WaitGroup{}

var meResp UserMe
var ratingResp RatingMe

wgcall.Add(2)
// The "me" and "rate" functions should be refactored to
// drop the wait group and channel arguments.
go func() {
    meResp = me(req)
    wgcall.Done()
}()
go func() {
    ratingResp = rate(req)
    wgcall.Done()
}()
wgcall.Wait()

// You are guaranteed that if "me" and "rate" returned valid values,
// they are populated in "meResp" and "ratingResp" at this point.

// Do whatever you need here, such as logging or returning.