用goroutine而非传统线程处理行情推送,因其轻量(2KB栈)和调度器自动负载均衡,适配高并发低延迟场景;单个WebSocket连接只需1个goroutine读取分发,下游消费应解耦为独立goroutine+缓冲channel(如64或128),配合context.Context安全关闭。

为什么用 goroutine 而不是传统线程处理行情推送
因为股票行情是典型的高并发、低延迟、小数据量高频更新场景,goroutine 的轻量级(初始栈仅 2KB)和调度器自动负载均衡,比系统线程更适配每秒数千次的 tick 推送。一个线程可能卡在阻塞 I/O 上,而 goroutine 在等待网络时会自动让出,不浪费资源。
常见错误是误以为“开越多 goroutine 越快”——实际中单个行情源(如 WebSocket 连接)本身是串行流,盲目为每个 tick 启一个 goroutine 反而引发调度抖动和内存碎片。真正该并发的地方是:多个行情源(不同股票代码)、下游消费(存储、计算、API 推送)解耦。
- 单个 WebSocket 连接只需 1 个
goroutine持续读取并分发tick - 下游如写入本地缓存、触发指标计算、广播给 HTTP 客户端,应各自独立
goroutine+channel管道 - 避免在
for range循环里直接调用go handleTick(tick)—— 若未加限流或缓冲 channel,极易 OOM
channel 缓冲大小怎么设才不丢数据也不拖慢
缓冲太小(比如 make(chan Tick, 1))在下游处理稍慢时立刻阻塞上游读取协程,导致行情断连;太大(如 10000)则掩盖性能瓶颈,内存占用飙升且延迟不可控。
真实行情系统中,缓冲大小取决于下游最慢环节的处理耗时与峰值吞吐。假设某只股票峰值每秒 500 tick,下游平均处理耗时 2ms,则积压上限约 500 * 0.002 = 1 条,但需预留突发余量——实践中 make(chan Tick, 64) 或 128 是较稳妥起点,再配合监控 len(ch) 动态告警。
立即学习“go语言免费学习笔记(深入)”;
- 不要用
unbuffered channel做行情分发,它要求发送和接收必须同时就绪,几乎必然卡死 - 若下游有多个消费者(如同时写 DB 和发 WebSocket),用
fan-out模式:一个inchannel → 多个带缓冲的outchannel - 用
select+default实现非阻塞发送,丢弃过期 tick 比阻塞更合理:“最新价”永远比“完整历史”重要
如何安全关闭 goroutine 链而不漏 tick 或 panic
行情程序常需热重启或切换行情源,硬杀 goroutine 会导致正在处理的 tick 中断、channel 关闭后继续写入 panic、甚至连接未释放。
核心是用 context.Context 统一控制生命周期,并配合 sync.WaitGroup 等待所有分支退出。关键点在于:读取协程负责监听 ctx.Done() 并主动关闭连接;所有写 channel 的地方都检查 ctx.Err() != nil;消费者用 for tick := range ch 自然退出(前提是上游已 close channel)。
- 切忌在任意位置调用
close(ch)—— 只有唯一发送方(通常是读取协程)才能 close,否则 panic - 下游消费者不应自己
close(ch),也不该用for { select { case 死循环,容易忽略 context 取消 - WebSocket 连接关闭前,先发
unsubscribe指令再等服务端确认,避免重连时重复订阅
真实行情场景下 time.Ticker 和 time.AfterFunc 别乱用
有人用 time.Ticker 定期拉行情,这完全违背实时性——交易所推送是事件驱动的,轮询不仅延迟高(至少一个 tick 周期),还增加服务器压力。真正要用的是 WebSocket 或 SSE 的长连接推送。
而 time.AfterFunc 常被误用于“超时重连”,但它生成的 goroutine 不受 parent context 管理,容易堆积。正确做法是用 context.WithTimeout 包裹连接建立逻辑,并在 defer 中 cancel。
- 行情解析逻辑里避免任何
time.Sleep—— 协程本就不该“睡”,而是靠 channel 阻塞或select等待事件 - 心跳检测用
conn.SetDeadline(net.Conn)或 WebSocket 库自带 ping/pong,而非另起 goroutine 定时发包 - 测试时模拟行情推送,用
time.After发送 mock tick 即可,别在生产代码里留time.Sleep(100 * time.Millisecond)
协程模型看着简单,但行情这种强时效+多依赖的场景,真正的难点从来不是“怎么开 goroutine”,而是“什么时候不该开”和“谁来关”。漏掉一个 WaitGroup.Done() 或多关一次 channel,线上就可能静默丢数据。


















