《Go小技巧易错点100例》第三十四篇
本期分享:
1.sync.Mutex锁复制导致的异常
2.Go堆栈机制下容易导致的并发问题
sync.Mutex锁复制导致的异常
以下代码片段存在一个隐蔽的并发安全问题:
type Counter struct {sync.MutexCount int
}func foo(c Counter) {c.Lock()defer c.Unlock()fmt.Println("in foo")
}func TestLock(t *testing.T) {var c Counterc.Lock()defer c.Unlock()c.Count++foo(c) // 复制锁
}
如果直接运行会出现异常:
fatal error: all goroutines are asleep - deadlock!
运行go vet .\code\code_34\lock_test.go
工具会直接提示警告:
.\code\code_34\lock_test.go:12:14: call of foo copies lock value: .\code\code_34\lock_test.go:4:6 contains sync.Mutex
Go堆栈机制下容易导致的并发问题
先看下下面这段代码,猜一下会输出什么:
func f() {l := []int{1, 2, 3, 4, 5} for _, i := range l { go fmt.Println(i) }
}
答案是:不确定。
我们来分析下代码,每个go fmt.Println(i)
会经历:
1)参数传递:将当前循环变量i的值拷贝到新goroutine的栈帧
2)上下文保存:创建新的G结构体(包含栈指针、PC等)
3)调度队列入队:将G加入全局运行队列或本地队列
4)上下文切换:可能触发当前M让出P,执行其他G
而这段代码的临时变量i存储在堆中,如果goroutine的执行在for循环结束,那么i的值就是5,此时输出则会是5 5 5 5 5
,当然如果goroutine执行快,那输出就可能是1 2 3 4 5
,因此是不确定的。
如果想要有正常1 2 3 4 5
的输出,那么我们只需要把代码改为:
func f() {l := []int{1, 2, 3, 4, 5} for _, i := range l { go func(i int) {fmt.Println(i) }(i)}
}
就可以了。