本期分享:
1.sync.Mutex锁复制导致的异常
2.Go堆栈机制下容易导致的并发问题
sync.Mutex锁复制导致的异常
以下代码片段存在一个隐蔽的并发安全问题:
type Counter struct {
sync.Mutex
Count int
}
func foo(c Counter) {
c.Lock()
defer c.Unlock()
fmt.Println("in foo")
}
func TestLock(t *testing.T) {
var c Counter
c.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)
}
}
就可以了。