for range遍历slice时不能直接取循环变量地址,因其复用同一内存地址,导致所有指针指向最后一次迭代值;正确做法是显式声明新变量(如v := item)或通过索引取址(&s[i])。

Go 语言不是“学完语法就能上手分布式系统”的语言——它把并发、错误传播、资源生命周期这些关键能力都藏在基础语法的细节里。跳过 defer 的执行时机、channel 的阻塞逻辑、context 的取消传递,直接写服务端代码,十有八九会在压测或上线后暴露竞态、泄漏或超时失控。
为什么 for range 遍历 slice 时不能直接用循环变量地址
常见错误是把循环变量取地址存进 map 或切片,结果所有条目指向同一个内存位置,最后全变成最后一次迭代的值。
- 根本原因是
for range复用同一个循环变量,每次迭代只是给它赋新值,而非新建变量 - 正确做法:显式声明新变量再取地址,例如
v := v; ptr := &v - 或者改用传统
for i := range s,再通过索引访问&s[i] - 这个坑在构建请求上下文、批量初始化结构体指针时高频出现,尤其搭配
goroutine后更隐蔽
channel 容量设为 0 还是 >0?实际影响远不止“是否阻塞”
零容量 channel(unbuffered)要求发送和接收必须同步完成;有容量则允许一定数量的发送先于接收。但选错容量会直接拖垮性能或掩盖逻辑缺陷。
- 用作信号通知(如任务结束):必须用
make(chan struct{}, 0),否则可能丢失信号 - 用于生产者-消费者解耦:容量通常设为预期并发峰值的 2–3 倍,但别盲目设大——缓冲区越大,内存占用越不可控,且延迟越高
- 注意:
len(ch)返回当前已排队数,cap(ch)才是容量;两者差值才是“还能塞几个”,别混淆 - 调试时用
select+default判断 channel 是否就绪,比轮询len()更可靠
context.WithTimeout 必须配对 defer cancel() 吗?
不是“必须”,而是“不配对就大概率泄漏 goroutine 或连接”。cancel 函数不只是释放 context,它还会关闭关联的内部 channel,触发所有监听该 context 的 goroutine 退出。
立即学习“go语言免费学习笔记(深入)”;
- 漏掉
defer cancel()最典型后果:HTTP handler 返回后,后台 goroutine 仍在跑,且持有的数据库连接、文件句柄不会释放 - 如果 context 是从上游传入(比如 HTTP 请求的
r.Context()),你不负责调用cancel;只有你自己调用WithCancel/WithTimeout创建的,才必须自己清理 - 嵌套 context 时,上级 cancel 会自动级联关闭下级,但下级不能反向影响上级——这是设计约束,不是 bug
- 用
go tool trace查 goroutine 泄漏时,常看到大量卡在runtime.gopark状态,源头往往就是没 cancel 的 context
真正卡住 Go 进阶的,从来不是 goroutine 怎么开,而是谁来关、何时关、关不干净会怎样。这些细节不写进日志,也不报 panic,只在高负载、长周期运行时慢慢浮现。


















