Go程序生命周期由main函数决定,其退出会导致所有goroutine强制终止;正确做法是用sync.WaitGroup显式同步:启动前Add、结束时Done、主goroutine调用Wait阻塞等待。

goroutine 退出后,main 函数为什么经常提前结束?
因为 Go 程序的生命周期由 main goroutine 决定——它一退出,整个进程立刻终止,其他正在运行的 goroutine 不会被等待。
常见错误现象:启动一个后台任务(比如日志轮转、心跳上报),但程序几秒后就退出,什么都没打印出来;或者用 go func() { time.Sleep(2 * time.Second); fmt.Println("done") }(),却始终看不到输出。
- 必须显式同步:用
sync.WaitGroup记录活跃的 goroutine 数量,defer wg.Done()在每个 goroutine 结束时调用,主 goroutine 调用wg.Wait()阻塞直到全部完成 - 避免用
time.Sleep“凑时间”:它不可靠,容易过短(没等完)或过长(浪费资源) - 注意
WaitGroup的 Add 必须在 goroutine 启动前调用,否则可能 panic:Add 和 Done 的调用顺序错乱会导致计数器负值
channel 关闭后还能读不能写,但为什么仍可能 panic?
关闭 channel 后,向它发送数据会立即 panic:send on closed channel;而接收操作会持续返回零值(如 0、""、nil),直到缓冲区耗尽,之后也返回零值。但问题常出在「多处并发写」或「重复关闭」。
典型场景:多个 goroutine 协同工作,都试图通过同一个 stopChan 通知退出;或者在 HTTP handler 中误把请求级 channel 当成全局 channel 关闭。
立即学习“go语言免费学习笔记(深入)”;
- 只允许一个 goroutine 关闭 channel:通常由 sender 或协调者负责,receiver 不应 close
- 用
select+default避免阻塞写:如果不确定 channel 是否已关,先用select { case ch 尝试发送 - 关闭前加判断(不推荐):Go 没有内置的「是否已关闭」检查函数,强行用 recover 捕获 panic 属于反模式
微服务中,context.WithTimeout 为什么总被忽略?
context.WithTimeout 不是“自动超时”,它只是创建一个带截止时间的 context.Context;真正起作用的是你是否把它传给下游函数,并且这些函数是否主动监听 ctx.Done()。
常见错误:在 RPC 调用前创建了带 timeout 的 context,但调用 client.Call(ctx, ...) 时传了另一个 context(比如 context.Background()),或 client 库本身没实现 context 取消逻辑。
- 必须逐层传递:HTTP client、gRPC client、数据库驱动(如 pgx)、甚至自定义的 long-running loop 都要接受
ctx参数,并在循环开头用select { case 检查 - 别用
context.TODO()替代:它只是占位符,没有取消能力,上线后等于放弃超时控制 - timeout 值要合理:太短导致频繁失败,太长则拖慢故障恢复;建议按 P95 延迟 + buffer 设定,而非拍脑袋填 30s
service mesh 场景下,为什么还手写重试和熔断?
Service Mesh(如 Istio)确实能提供统一的重试、超时、熔断策略,但这些策略只作用于「网络层」——即 L4/L7 流量转发。一旦请求进入你的 Go 微服务进程内部,mesh 就不再干预。而业务逻辑中的重试(比如 DB 写失败后重试)、本地缓存更新失败、或跨多个下游服务的组合调用,仍需代码层控制。
例如:一个订单创建接口,需先后调用库存服务、支付服务、通知服务;即使 mesh 对每个 HTTP 调用都配置了重试,也无法保证这三步整体原子性,更无法在第二步失败时回滚第一步。
- 用
github.com/sony/gobreaker或github.com/cenkalti/backoff/v4实现应用层熔断和指数退避重试 - 重试要带 context:每次重试都应新建子 context,避免上层 timeout 被反复延长
- 熔断状态不应跨请求共享:每个服务实例维护自己的熔断器,避免单点故障放大成全量拒绝
真正的难点不在记住 API,而在判断「该在哪一层做控制」:网络层交给 mesh,协议层用标准库(net/http、grpc),业务层自己编码——三者边界模糊时,最容易漏掉关键路径的超时或取消。


















