default不是超时机制,因为它只在所有case均阻塞时立即执行,不等待、不计时、不阻塞,本质是非阻塞轮询;而超时需等待指定时间后触发,必须用timer.C或context.Done()接入select才能实现。

超时重试不能靠 default + time.Sleep 模拟,必须用 timer.C 或 context.Done() 接入 select。
为什么 default 不是超时机制
default 分支在 select 中的作用是“立即执行”,它不等待、不计时、不阻塞。只要所有 case 都不可就绪,default 就立刻跑一次——这本质是轮询,不是延时。
- 误用示例:
select { case → 这不是超时,是 busy-wait,CPU 占用飙升 - 真正超时需要 channel 信号:要么
time.After(d),要么timer.C,要么ctx.Done() - default 只适合轻量探测:比如检查
ch是否为空(select { case v, ok := ),或发心跳、记日志
超时重试的正确写法:timer.NewTimer + defer Stop()
频繁调用 time.After 会持续创建 timer,触发 GC 压力;用 time.NewTimer 才可控。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 每次重试前重置 timer:
timer.Reset(timeoutFunc(attempt)) - 务必
defer timer.Stop(),避免 goroutine 泄漏 - 不要在循环里反复 new timer,复用一个更安全
- 示例关键片段:
timer := time.NewTimer(timeoutFunc(attempt)) defer timer.Stop() select { case data := <-ch: handle(data) case <-timer.C: log.Printf("timeout on attempt %d", attempt) }
结合 context.WithTimeout 实现取消感知重试
当超时需联动 cancel、重试计数、错误分类时,硬编码 timer 容易失控;context.WithTimeout 更可靠。
立即学习“go语言免费学习笔记(深入)”;
ctx, cancel := context.WithTimeout(parentCtx, timeoutFunc(attempt))- 把
ctx.Done()直接放进 select:case - cancel() 必须在退出前调用,否则 context 泄漏
- 注意:
ctx.Err()在超时时返回context.DeadlineExceeded,可据此做错误分流
容易被忽略的点:select 的求值顺序与 case 就绪判定
select 不是“先看哪个 case 写在前面”,而是先求值所有 channel 表达式,再统一判断是否就绪——这意味着:
- 如果某个
case ch 中的 <code>v是函数调用,它会在 select 开始时就被执行,哪怕最终没选中这个 case - 无缓冲 channel 的接收 case 就绪,要求“另一端有 goroutine 正在发送”;仅 channel 非空不够
- 多个 case 同时就绪时随机选择,无法预测顺序——别依赖 case 排列顺序做逻辑控制

















