根本原因是缺乏背压控制和连接生命周期管理,导致客户端断连后goroutine持续阻塞堆积,引发内存暴涨、gc频繁和响应变慢;必须在每个goroutine内做连接健康检查与panic捕获,写入前校验http.flusher可用性并用select+context超时保障及时退出。

goroutine 处理音视频流时,为什么一开就崩
根本原因不是并发太多,而是没做背压控制和连接生命周期管理。比如用 http.ResponseWriter 推送 SSE 流时,goroutine 一旦启动就不管客户端是否断连,最后堆积成千上万个死 goroutine,内存暴涨、GC 频繁、响应变慢。
必须在每个流处理 goroutine 内部加连接健康检查和 panic 捕获:
- 每次写入前检查
w.(http.Flusher)是否可用,不可用就break - 用
select { case 响应上下文取消 - 最外层包
defer func() { if r := recover(); r != nil { log.Printf("stream panic: %v", r) } }()
channel 缓冲设成 0 还是 1024?关键看数据源特性
音视频流对延迟和顺序敏感,缓冲不是越大越稳——它本质是“时间换空间”的权衡。传感器采样、RTP 包解析这类强时序场景,make(chan []byte, 0)(无缓冲)反而是首选:写入即阻塞,天然限速,避免下游来不及处理时堆满旧帧丢新帧。
只有在明确知道下游吞吐下限且允许少量积压时,才设缓冲:
- Kafka 拉取音视频元数据,每秒 300 条、单条处理 6ms → 理论积压上限 1.8 条 →
make(chan Event, 4)足够 - UDP 接收 RTP 包,用
make(chan *rtp.Packet, 64)配合recvmmsg批量读取,避免频繁系统调用 - 绝对别用
chan []byte直接传原始视频帧;改传*VideoFrame指针或 ID,再异步加载,减少堆分配
多个 goroutine 读同一 UDP conn,怎么避免丢包
Go 的 net.UDPConn 本身是线程安全的,但多个 goroutine 并发 ReadFrom 会竞争底层 socket 缓冲区,导致部分包被覆盖或静默丢弃——这不是 Go bug,是 UDP 协议栈行为。
正确做法是只起一个 goroutine 负责读,再用 channel 或 sync.Pool 分发:
- 单读 goroutine + 无缓冲 channel:
for { n, addr, err := conn.ReadFrom(buffer); ch - 若需更高吞吐,改用
recvmmsg(需 cgo 或第三方库如golang.org/x/net/ipv4),一次收多包再分发 - buffer 必须从
sync.Pool获取,避免高频 GC;defer pool.Put(buffer)放回池中
实时转码调度中,goroutine 泄漏最常发生在哪
不是 FFmpeg 子进程没 kill,而是转码完成回调里忘了关 channel 或清理定时器。典型泄漏点:
- 用
time.AfterFunc设置超时后,转码成功却没调timer.Stop(),timer 持有 goroutine 引用直到超时 - 转码结果写入
chan Result后,主 goroutine 已退出但 channel 未关闭,发送 goroutine 永久阻塞在ch - WebSocket 推流时,客户端断连后没 close 对应的
done chan struct{},导致 cleanup goroutine 不退出
上线前必跑:go tool pprof http://localhost:6060/debug/pprof/goroutine?debug=2,重点看数量持续增长的 goroutine 栈,90% 的泄漏都卡在 channel 发送或 timer 上。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











