You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从Goroutine中正确获取返回值?

问题分析与解决方案

业务流程

  • 向API发送GET请求,获取所有事件ID
  • 使用每个事件ID构造URI,发送另一个GET请求
  • 解析响应并返回结果

当前代码问题

你的实现中,带结果返回的版本运行异常,核心问题如下:

  1. WaitGroup计数错误:main中仅调用wg.Add(1),但实际启动了1个getSports和3个getOdds goroutine,导致wg.Wait()无法正确等待所有goroutine完成。
  2. 通道未关闭:getSports发送完所有事件ID后未关闭chnl,导致getOdds中的死循环会一直尝试读取通道,即使无数据也不会退出。
  3. 结果接收逻辑缺陷:main中仅接收3次chnlr的数据,若getSports发送的事件ID多于3个,后续getOdds发送结果时会因无接收者阻塞,最终引发goroutine泄漏或死锁。

修正后的代码实现

核心优化点

  • getSports发送完所有事件ID后关闭chnl,让getOdds感知数据发送完毕并退出循环
  • 正确维护WaitGroup计数,每个goroutine启动前调用wg.Add(1)
  • 用单独goroutine接收所有结果,避免阻塞getOdds执行
  • 移除getOdds中的死循环,改为遍历通道直到关闭
import (
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
)

// 替换为实际API地址
var API = map[string]string{
    "sports": "YOUR_SPORTS_API_ENDPOINT",
    "odds":   "YOUR_ODDS_API_ENDPOINT",
}

// 替换为实际允许的SportID映射
var SportsMap = map[int]struct{}{
    1: {},
    2: {},
}

type Sport []struct {
    Payload struct {
        CompetitionsWithEvents []struct {
            Competition struct {
                SportID int `json:"sportID"`
            } `json:"competition"`
            Events []struct {
                ID string `json:"id"`
            } `json:"events"`
        } `json:"competitionsWithEvents"`
    } `json:"payload"`
}

// 替换为你的实际转换逻辑
func MakeMeta(body []byte) interface{} {
    var data interface{}
    _ = json.Unmarshal(body, &data)
    return data
}

type scanner struct{}

func main() {
    client := tools.HttpClient() // 保留你的HTTP客户端
    wg := new(sync.WaitGroup)

    chnl := make(chan string)
    chnlr := make(chan string)
    scann := &scanner{}

    // 启动事件ID获取goroutine
    wg.Add(1)
    go scann.getSports(chnl, client, wg)

    // 启动3个详情请求goroutine
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go scann.getOdds(chnl, chnlr, client, wg)
    }

    // 单独goroutine接收所有结果,避免阻塞主流程
    var results []string
    wg.Add(1)
    go func() {
        defer wg.Done()
        for res := range chnlr {
            results = append(results, res)
        }
    }()

    // 等待所有数据生产goroutine完成
    wg.Wait()
    // 关闭结果通道,通知接收goroutine退出
    close(chnlr)

    // 输出所有处理结果
    for _, res := range results {
        fmt.Println(res)
    }
}

// getSports 获取事件ID并发送到通道,完成后关闭通道
func (*scanner) getSports(chnl chan<- string, client *http.Client, wg *sync.WaitGroup) {
    defer wg.Done()
    // 发送请求并获取响应
    body := tools.GetRequest(client, "GET", API["sports"])
    // 解析JSON响应
    var s Sport
    if err := json.Unmarshal(body, &s); err != nil {
        fmt.Println("解析体育事件响应失败:", err)
        close(chnl)
        return
    }

    var count int16
    for _, rawdata := range s {
        for _, sport := range rawdata.Payload.CompetitionsWithEvents {
            // 过滤允许的SportID
            if _, ok := SportsMap[sport.Competition.SportID]; !ok {
                continue
            }
            for _, event := range sport.Events {
                chnl <- event.ID
                count++
            }
        }
    }
    fmt.Println("[Total events] -", count)
    // 关闭通道,通知下游goroutine数据已发送完毕
    close(chnl)
}

// getOdds 从通道获取事件ID,请求详情并发送结果到结果通道
func (*scanner) getOdds(chnl <-chan string, chnlr chan<- string, client *http.Client, wg *sync.WaitGroup) {
    defer wg.Done()

    // 遍历通道数据,直到通道关闭
    for eventID := range chnl {
        // 构造请求URI
        uri := fmt.Sprintf("%s%s:", API["odds"], eventID)
        // 发送详情请求
        body := tools.GetRequest(client, "GET", uri)
        converted := MakeMeta(body)

        result, err := json.MarshalIndent(converted, "", " ")
        if err != nil {
            fmt.Println("序列化结果失败:", err)
            continue
        }
        chnlr <- string(result)
    }
}

关键说明

  1. 通道关闭机制:getSports完成数据发送后关闭chnl,getOdds通过for eventID := range chnl自动感知通道关闭,循环退出后goroutine正常结束。
  2. WaitGroup正确管理:每个goroutine启动前调用wg.Add(1),确保所有Done()调用都被计数,wg.Wait()能准确等待所有任务完成。
  3. 无阻塞结果接收:单独用goroutine接收所有结果,避免主流程阻塞,同时保证getOdds发送的所有结果都能被处理,不会因接收者不足导致阻塞。

内容的提问来源于stack exchange,提问作者SonyMag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 09:30:51