根本原因是多个goroutine向已关闭的channel发送数据;应使用带缓冲channel或sync.waitgroup收集结果,仅主goroutine关闭channel,并配合http.client连接池调优与令牌桶限流防429。

goroutine发短信验证码时,为什么并发数一高就panic: send on closed channel?
根本原因是多个goroutine往同一个已关闭的chan里写,或者没控制好关闭时机。常见于用channel做结果收集但没配同步机制。
正确做法是:用带缓冲的channel接收结果,或改用sync.WaitGroup + 闭包变量收集;channel只在所有goroutine结束后由主goroutine关闭。
- 别在worker goroutine里关channel,除非你明确做了唯一性判断(比如用
sync.Once) - 缓冲区大小建议设为预期并发数,例如
make(chan error, 100)对应100个验证码请求 - 如果只是发完就丢结果,直接用
sync.WaitGroup更轻量,避免channel阻塞风险
用http.Client配合goroutine批量调用短信API,如何避免Too Many Requests?
不是起越多goroutine越快,而是受限于目标API的限流策略和本地HTTP连接池。盲目并发只会触发429或连接超时。
关键在复用http.Client并调优Transport:
- 设置
MaxIdleConns和MaxIdleConnsPerHost为合理值(如20~50),避免端口耗尽 - 显式设置
Timeout,防止单个请求卡住整个goroutine池,例如&http.Client{Timeout: 10 * time.Second} - 加一层简单令牌桶或计数器,例如用
time.Ticker控制每秒最多发N条,比纯goroutine更可控
示例节流逻辑:
rateLimiter := time.Tick(100 * time.Millisecond) // 每100ms放行1个
for _, phone := range phones {
<h3>sendSMS函数里必须检查哪些错误才不会漏发或重复发?</h3><p>短信发送失败不等于网络失败——运营商返回“号码格式错误”“余额不足”“签名未审核”都算业务错误,需要区分处理。</p>
- 网络层错误(
err != nil且resp == nil):重试1次,间隔200ms+ - HTTP状态码非200(如400/401/429/500):记录错误码和响应体,不要重试,尤其是401(密钥失效)、429(被限流)
- 响应体JSON解析失败:说明API格式异常,需告警,暂停该通道下发
- 业务错误码(如腾讯云的
Code: "InvalidPhoneNumber"):记录后跳过,不计入失败重试计数
典型响应结构要提前约定好,比如统一用{"code":0,"msg":"OK","data":{"sid":"xxx"}},避免每次解析逻辑散落各处。
生产环境goroutine数量怎么定才不OOM也不压垮短信服务商?
没有固定值,取决于三个变量:单请求平均内存占用、短信API的TPS上限、你的服务器可用CPU/内存。硬设runtime.GOMAXPROCS(4)没用,goroutine本身很轻,瓶颈在HTTP连接和远程服务。
- 先用10并发压测,看平均耗时和错误率;再逐步翻倍,直到错误率突增或P99延迟超过2s
- 把并发数做成配置项(如
maxWorkers: 30),上线后根据监控动态调,别写死在代码里 - 务必加超时控制:每个goroutine启动时传入
context.WithTimeout(ctx, 8*time.Second),避免个别请求拖垮整批
最容易被忽略的是日志打点——每个goroutine里别用全局logger直接打,要用带traceID的实例,否则出问题根本分不清哪条是哪个手机号的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











