Golang中Run()执行命令无返回,音乐流服务请求阻塞问题求助
问题描述
尝试用youtube-dl和ffmpeg搭建音乐流服务,基于Gin框架处理POST请求:当用户发送携带视频URL的请求时,通过io.Pipe连接youtube-dl和ffmpeg,将视频转成MP3流返回。但测试时请求始终无法完成,日志不输出"Done-----------------------",推测进程阻塞。
相关代码如下:
router.POST("/submit", func(c *gin.Context) { body := Body{} if err := c.BindJSON(&body); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } w := c.Writer header := w.Header() header.Set("Content-Type", "audio/mp3") w.WriteHeader(http.StatusOK) fetchMusic(c, body.Data) }) func fetchMusic(c *gin.Context, data string) { r, w := io.Pipe() defer r.Close() ydl := exec.Command("youtube-dl", data, "-o-") ffmpeg := exec.Command("ffmpeg", "-i", "/dev/stdin", "-vn", "-f", "mp3", "-") ydl.Stdout = w ydl.Stderr = os.Stderr ffmpeg.Stdin = r ffmpeg.Stdout = c.Writer ffmpeg.Stderr = os.Stderr fmt.Println("Starting-----------------------") go func() { if err := ydl.Run(); err != nil { panic(err) } }() if err := ffmpeg.Run(); err != nil { panic(err) } fmt.Println("Done-----------------------") }
问题分析与解决
核心问题:io.Pipe的Writer未关闭
io.Pipe的读写端是同步阻塞的:当youtube-dl执行完成后,你没有关闭w(Pipe的Writer),导致ffmpeg一直认为还有输入数据,会持续阻塞在读取stdin的状态,永远不会退出,所以ffmpeg.Run()无法完成,自然不会打印"Done"。
修复步骤
- 关闭Pipe的Writer:在youtube-dl执行完成(无论成功或失败)后,必须关闭
w,让ffmpeg知道输入流已经结束。 - 替换panic为合理的错误处理:直接panic会导致整个服务崩溃,应该用日志记录错误,或者通过Gin上下文返回错误给客户端。
- 监听请求取消信号(可选但推荐):如果客户端中途断开连接,终止子进程,避免资源浪费。
修改后的代码
import ( "io" "os/exec" "log" "net/http" "github.com/gin-gonic/gin" ) router.POST("/submit", func(c *gin.Context) { body := Body{} if err := c.BindJSON(&body); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } c.Writer.Header().Set("Content-Type", "audio/mp3") c.Status(http.StatusOK) if err := fetchMusic(c, body.Data); err != nil { log.Printf("处理音乐流失败: %v", err) // 如果还没开始写响应,可以返回错误;否则只能记录日志 if !c.Writer.Written() { c.AbortWithError(http.StatusInternalServerError, err) } } }) func fetchMusic(c *gin.Context, data string) error { r, w := io.Pipe() defer r.Close() // 确保Writer最终关闭 defer w.Close() ydl := exec.Command("youtube-dl", data, "-o-") ffmpeg := exec.Command("ffmpeg", "-i", "/dev/stdin", "-vn", "-f", "mp3", "-") ydl.Stdout = w ydl.Stderr = os.Stderr ffmpeg.Stdin = r ffmpeg.Stdout = c.Writer ffmpeg.Stderr = os.Stderr log.Println("Starting-----------------------") // 启动youtube-dl的goroutine,执行完成后关闭Writer go func() { err := ydl.Run() // 必须关闭Writer,即使youtube-dl执行失败 if closeErr := w.Close(); closeErr != nil { log.Printf("关闭Pipe Writer失败: %v", closeErr) } if err != nil { log.Printf("youtube-dl执行失败: %v", err) } }() // 绑定请求取消信号,客户端断开时终止子进程 ctx := c.Request.Context() go func() { <-ctx.Done() _ = ydl.Process.Kill() _ = ffmpeg.Process.Kill() }() // 等待ffmpeg执行完成 if err := ffmpeg.Run(); err != nil { return err } log.Println("Done-----------------------") return nil }
关键说明
- 关闭Pipe Writer:
defer w.Close()确保无论流程如何,Writer都会被关闭;同时在youtube-dl执行完后主动关闭,让ffmpeg及时感知输入结束。 - 错误处理:用日志记录错误,避免panic导致服务崩溃;同时检查响应是否已写入,决定是否返回错误给客户端。
- 请求取消监听:通过
c.Request.Context()监听客户端断开事件,及时终止子进程,防止僵尸进程占用资源。
内容的提问来源于stack exchange,提问作者H.Okan
相关产品推荐
相关产品推荐

