如何从Goroutine中正确获取返回值?
问题分析与解决方案
业务流程
- 向API发送GET请求,获取所有事件ID
- 使用每个事件ID构造URI,发送另一个GET请求
- 解析响应并返回结果
当前代码问题
你的实现中,带结果返回的版本运行异常,核心问题如下:
- WaitGroup计数错误:main中仅调用
wg.Add(1),但实际启动了1个getSports和3个getOddsgoroutine,导致wg.Wait()无法正确等待所有goroutine完成。 - 通道未关闭:
getSports发送完所有事件ID后未关闭chnl,导致getOdds中的死循环会一直尝试读取通道,即使无数据也不会退出。 - 结果接收逻辑缺陷: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) } }
关键说明
- 通道关闭机制:
getSports完成数据发送后关闭chnl,getOdds通过for eventID := range chnl自动感知通道关闭,循环退出后goroutine正常结束。 - WaitGroup正确管理:每个goroutine启动前调用
wg.Add(1),确保所有Done()调用都被计数,wg.Wait()能准确等待所有任务完成。 - 无阻塞结果接收:单独用goroutine接收所有结果,避免主流程阻塞,同时保证
getOdds发送的所有结果都能被处理,不会因接收者不足导致阻塞。
内容的提问来源于stack exchange,提问作者SonyMag
相关产品推荐
相关产品推荐

