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

为何我的Go函数未等待goroutine执行完成?

问题分析

你的代码核心问题在于启动goroutine后立即读取未完成执行的结果:

  1. 循环中启动goroutine后,立刻访问ed的Error和EncodedData,此时goroutine还未执行,这些值都是初始零值,导致results[i].IMG被提前赋值为空的base64前缀。
  2. 未关闭HTTP响应体,存在资源泄漏风险。
  3. 错误处理时机错误,goroutine还未执行就检查错误,完全起不到作用。
解决方案

1. 修复GetPainting函数(资源泄漏+健壮性)

import (
    "encoding/base64"
    "errors"
    "io/ioutil"
    "net/http"
    "sync"
)

type EncodedData string

type EncodedImage struct {
    Data        []byte
    EncodedData EncodedData
    Error       error
}

func GetPainting(url string, EI *EncodedImage) {
    res, err := http.Get(url)
    if err != nil {
        EI.Error = errors.Wrapf(err, "unable to fetch from provided url %s", url)
        return
    }
    defer res.Body.Close() // 必须关闭响应体,避免连接泄漏

    // 检查HTTP响应状态码
    if res.StatusCode != http.StatusOK {
        EI.Error = errors.Errorf("unexpected status code: %d for url %s", res.StatusCode, url)
        return
    }

    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        EI.Error = errors.Wrapf(err, "failed to read response body from %s", url)
        return
    }

    encoded := base64.StdEncoding.EncodeToString(body)
    EI.Data, EI.EncodedData = body, EncodedData(encoded)
}

2. 修正Gin处理器的并发逻辑

func Search(db *gorm.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        term := c.Param("term")
        var results []models.Searches
        // 先处理DB查询错误
        if err := db.Table("searches").Where("to_tsvector(\"searches\".\"Title\" || '' || \"searches\".\"Artist_Name\") @@ plainto_tsquery(?)", term).Find(&results).Error; err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        var wg sync.WaitGroup
        // 创建与results长度匹配的切片,存储每个goroutine的结果
        encodedImages := make([]EncodedImage, len(results))
        // 可选:限制并发数,避免大量HTTP请求耗尽资源
        sem := make(chan struct{}, 10)

        for i := range results {
            sem <- struct{}{}
            wg.Add(1)
            // 通过闭包传递索引,避免goroutine捕获循环变量的问题
            go func(idx int) {
                defer func() {
                    wg.Done()
                    <-sem
                }()
                GetPainting(results[idx].IMG, &encodedImages[idx])
            }(i)
        }

        // 等待所有goroutine执行完成
        wg.Wait()

        // 统一处理错误+更新结果
        for i := range results {
            if encodedImages[i].Error != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"error": encodedImages[i].Error.Error()})
                return
            }
            results[i].IMG = fmt.Sprintf("data:image/jpeg;base64,%v", encodedImages[i].EncodedData)
        }

        c.JSON(http.StatusOK, results)
    }
}

关键修改说明

  • 资源泄漏修复:给res.Body加上defer res.Body.Close(),确保HTTP连接被正确释放。
  • 并发逻辑修正:
    1. 创建encodedImages切片存储每个goroutine的结果,等待所有goroutine完成后再统一更新results。
    2. 使用带索引的闭包传递循环变量,避免goroutine捕获到复用的循环变量。
    3. 新增并发数限制(sem通道),防止因结果数量过大导致的系统资源耗尽。
  • 错误处理优化:将错误检查移到所有goroutine执行完成后,避免单个请求错误导致其他goroutine资源泄漏,同时确保只有全部请求成功才返回最终结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 19:30:47