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

Go语言:单goroutine配WaitGroup上传文件为何死锁?

问题分析与解决方案

死锁原因

你遇到的死锁是因为**wg.Wait()的位置错误**:如果把wg.Wait()放在client.Do(req)之前,main goroutine会等待子goroutine完成,但子goroutine在执行io.Copy(part, file)时,会往io.Pipe的writer写数据,而io.Pipe的写操作会阻塞,直到对应的reader被读取。此时main goroutine卡在wg.Wait(),没有任何goroutine去读取reader,子goroutine也卡在写操作,最终所有goroutine休眠,触发死锁。

核心上传问题

大文件上传返回No file chosen,本质是请求发送时multipart数据未完全写入:小文件写入速度快,子goroutine能在client.Do读取数据前完成写入;但大文件写入耗时久,client.Do提前发送了不完整的请求体,导致API无法识别到文件。

修复方案

不需要使用WaitGroup,利用io.Pipe的同步读写特性即可,同时修正错误处理逻辑:

package main

import (
    "fmt"
    "io"
    "log"
    "mime/multipart"
    "net/http"
    "os"
)

func main() {
    client := http.Client{}

    // 打开要上传的文件
    file, err := os.Open("./wallpaper.jpg")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    reader, writer := io.Pipe()
    mpWriter := multipart.NewWriter(writer)

    // 启动goroutine写入multipart数据
    errChan := make(chan error, 1)
    go func() {
        defer writer.Close()
        defer mpWriter.Close()

        fmt.Println("Starting Upload...")
        // 创建表单文件字段
        part, err := mpWriter.CreateFormFile("file", file.Name())
        if err != nil {
            errChan <- err
            return
        }

        fmt.Println("Copying file data...")
        _, err = io.Copy(part, file)
        errChan <- err
    }()

    // 构建HTTP请求
    req, err := http.NewRequest(http.MethodPost, "https://api.anonfiles.com/upload", reader)
    if err != nil {
        log.Fatal(err)
    }
    // 设置正确的Content-Type(包含multipart boundary)
    req.Header.Set("Content-Type", mpWriter.FormDataContentType())

    // 发送请求(会自动同步读取reader的数据,触发goroutine的写入操作)
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    // 检查goroutine是否有错误
    if err := <-errChan; err != nil {
        log.Fatal(err)
    }

    // 读取响应
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(body))
}

关键改进点

  1. 用错误通道替代log.Fatal:子goroutine的错误通过errChan传递到main goroutine处理,避免直接退出程序导致资源未关闭。
  2. 利用io.Pipe同步特性:client.Do读取reader时会自动阻塞,等待goroutine写入数据,无需额外WaitGroup协调。
  3. 正确的资源关闭顺序:goroutine里先关闭multipart writer(确保写入结束标记),再关闭io.Pipe的writer,保证reader能读到完整的multipart数据。

额外说明

  • 不要在子goroutine中使用log.Fatal,它会直接调用os.Exit,导致所有defer操作失效,可能引发资源泄漏或阻塞。
  • 确保请求头的Content-Type使用mpWriter.FormDataContentType(),它会自动包含正确的multipart boundary,否则API无法解析请求体。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 19:20:46