这里列举的Go语言常见坑都是符合Go语言语法的, 可以正常的编译, 但是可能是运行结果错误, 或者是有资源泄漏的风险.
数组是值传递
在函数调用参数中, 数组是值传递, 无法通过修改数组类型的参数返回结果.
func main() {
x := [3]int{1, 2, 3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr)
}(x)
fmt.Println(x)
}
必要时需要使用切片.
map遍历是顺序不固定
map是一种hash表实现, 每次遍历的顺序都可能不一样.
func main() {
m := map[string]string{
"1": "1",
"2": "2",
"3": "3",
}
for k, v := range m {
println(k, v)
}
}
返回值被屏蔽
在局部作用域中, 命名的返回值内同名的局部变量屏蔽:
func Foo() (err error) {
if err := Bar(); err != nil {
return
}
return
}
recover必须在defer函数中运行
recover捕获的是祖父级调用时的异常, 直接调用时无效:
func main() {
recover()
panic(1)
}
直接defer调用也是无效:
func main() {
defer recover()
panic(1)
}
defer调用时多层嵌套依然无效:
func main() {
defer func() {
func() { recover() }()
}()
panic(1)
}
必须在defer函数中直接调用才有效:
func main() {
defer func() {
recover()
}()
panic(1)
}
main函数提前退出
后台Goroutine无法保证完成任务.
func main() {
go println("hello")
}
通过Sleep来回避并发中的问题
休眠并不能保证输出完整的字符串:
func main() {
go println("hello")
time.Sleep(time.Second)
}
类似的还有通过插入调度语句:
func main() {
go println("hello")
runtime.Gosched()
}
独占CPU导致其它Goroutine饿死
Goroutine是协作式调度, Goroutine本身不会主动放弃CPU:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {} // 占用CPU
}
解决的方法是在for循环加入runtime.Gosched()调度函数:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {
runtime.Gosched()
}
}
或者是通过阻塞的方式避免CPU占用:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
select{}
}
不同Goroutine之间不满足顺序一致性内存模型
因为在不同的Goroutine, main函数可能无法观测到done的状态变化, 那么for循环会陷入死循环:
var msg string
var done bool = false
func main() {
runtime.GOMAXPROCS(1)
go func() {
msg = "hello, world"
done = true
}()
for {
if done {
println(msg)
break
}
}
}
解决的办法是用显示同步:
var msg string
var done = make(chan bool)
func main() {
runtime.GOMAXPROCS(1)
go func() {
msg = "hello, world"
done <- true
}()
<-done
println(msg)
}
闭包错误引用同一个变量
func main() {
for i := 0; i < 5; i++ {
defer func() {
println(i)
}()
}
}
改进的方法是在每轮迭代中生成一个局部变量