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)) }
关键改进点
- 用错误通道替代
log.Fatal:子goroutine的错误通过errChan传递到main goroutine处理,避免直接退出程序导致资源未关闭。 - 利用
io.Pipe同步特性:client.Do读取reader时会自动阻塞,等待goroutine写入数据,无需额外WaitGroup协调。 - 正确的资源关闭顺序:goroutine里先关闭multipart writer(确保写入结束标记),再关闭io.Pipe的writer,保证reader能读到完整的multipart数据。
额外说明
- 不要在子goroutine中使用
log.Fatal,它会直接调用os.Exit,导致所有defer操作失效,可能引发资源泄漏或阻塞。 - 确保请求头的
Content-Type使用mpWriter.FormDataContentType(),它会自动包含正确的multipart boundary,否则API无法解析请求体。
内容的提问来源于stack exchange,提问作者TheLawlessDev
相关产品推荐
相关产品推荐

