1. for range
循环的副本陷阱
- 问题:在
for range
循环中,迭代变量是值的副本,而非原始元素的引用。若在循环内获取元素的地址,所有迭代会指向同一内存地址。
var out []*int
for _, v := range []int{1, 2, 3} {
out = append(out, &v) // 错误:所有指针指向同一个v的地址
}
修复:显式创建局部变量或直接使用索引:
for i := range list {
v := list[i] // 创建新变量
out = append(out, &v)
}
2. 并发编程中的Channel问题
- 向已关闭的Channel发送数据:会导致
panic
,且无法通过recover
捕获。 - 无缓冲Channel的阻塞:若接收方未准备好,发送方会永久阻塞。例如超时场景下子协程可能泄露:
ch := make(chan int) // 无缓冲
go func() { ch <- 1 }()
select {
case <-ch: // 正常接收
case <-time.After(1 * time.Second): // 超时后子协程阻塞
}
修复:使用带缓冲的Channel或context
控制超时。
3. Slice和Map的共享引用
- Slice的“隐藏”数据:对Slice的切片操作可能意外保留底层数组的引用,导致内存泄漏。
func leak() []byte {
bigData := make([]byte, 1e6)
return bigData[:10] // 底层数组仍被引用
}
修复:使用copy
复制所需数据。
4. 错误处理与panic
滥用
- 忽略错误返回值:Go鼓励显式错误处理,但开发者可能忽略
error
返回值,导致潜在问题。 - 不当使用
panic
:panic
应用于不可恢复错误,但滥用会导致程序崩溃。例如配置文件加载失败应返回error
而非panic
。
5. Goroutine泄露
- 未正确管理Goroutine生命周期:例如未调用
WaitGroup.Done()
或未关闭Channel,导致Goroutine永久阻塞。
var wg sync.WaitGroup
for _, task := range tasks {
wg.Add(1)
go func() {
defer wg.Done() // 若遗漏,Wait()会阻塞
process(task)
}()
}
wg.Wait()
6. 接口与nil
的混淆
nil
接口值判断错误:接口变量为nil
时,其动态类型和值均为nil
,但若仅动态值为nil
而类型非nil
,接口判断仍为非nil
。
var buf *bytes.Buffer // buf为nil
var w io.Writer = buf // w非nil(动态类型为*bytes.Buffer)
7. defer
在循环中的误用
- 循环内
defer
导致资源阻塞:defer
在函数退出时执行,若在循环中使用defer
释放锁或资源,可能导致后续迭代阻塞。
for _, p := range persons {
mutex.Lock()
defer mutex.Unlock() // 错误:锁在循环结束后才释放
p.Age = 13
}
修复:将defer
移至匿名函数中。
避免掉入陷阱:
- 代码审查:重点关注并发操作、资源管理和错误处理。
- 工具辅助:使用
go vet
和-race
标志检测竞争条件。 - 测试覆盖:针对边界条件(如空Slice、超时)编写单元测试。