essay
数组、切片、map在并发情况下
#go
数组、切片、map在并发情况下会怎么样?
在 Go 里,并发访问数据时最重要的一条规则是:
多个 goroutine 同时访问同一块内存,只要其中至少有一个是写操作,并且没有使用锁、channel、原子操作等同步手段,就会产生数据竞争。
对切片(数组)模拟多个goroutine并发修改:
func main() {
a := []int{1, 2, 3, 4}
var sw sync.WaitGroup
for i := 0; i < 100; i++ {
sw.Add(1)
go func(val int) {
defer sw.Done()
a[0] = val + 10
}(i)
}
sw.Wait()
fmt.Println(a, len(a))
}直接运行输出:
zhangjing@zhangjingdeMacBook-Pro-4237 test % go run test06.go
[41 2 3 4] 4可以看出程序并不会崩溃,使用go run -race test06.go命令查看数据竞争警告,输出:
zhangjing@zhangjingdeMacBook-Pro-4237 test % go run -race test06.go
==================
WARNING: DATA RACE
Write at 0x00c000142000 by goroutine 8:
main.main.func2()
/Users/zhangjing/Desktop/interesting/test/test06.go:19 +0x78
Previous write at 0x00c000142000 by goroutine 7:
main.main.func1()
/Users/zhangjing/Desktop/interesting/test/test06.go:14 +0x78
Goroutine 8 (running) created at:
main.main()
/Users/zhangjing/Desktop/interesting/test/test06.go:17 +0x1e8
Goroutine 7 (finished) created at:
main.main()
/Users/zhangjing/Desktop/interesting/test/test06.go:12 +0x140
==================
[20 2 3 4]
Found 1 data race(s)
exit status 66那对切片模拟多个goroutine并发append呢?
func main() {
a := []int{1, 2, 3, 4}
var sw sync.WaitGroup
for i := 0; i < 10; i++ {
sw.Add(1)
go func(val int) {
defer sw.Done()
a = append(a, val+10)
}(i)
}
sw.Wait()
fmt.Println(a, len(a))
}输出:
[1 2 3 4 19 13 14 15 16 17 18 10 12 11] 14可以看出切片的长度并不是原来的4+10(goroutine),是因为存在多个goroutine同时append,会出现先append的值被覆盖的情况。
对map模拟多个goroutine并发修改(包括append):
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
var sw sync.WaitGroup
for i := 0; i < 100; i++ {
sw.Add(1)
go func(val int) {
defer sw.Done()
m["d"] = val + 10
}(i)
}
sw.Wait()
fmt.Println(m, len(m))
}输出:
fatal error: concurrent map writes
goroutine 99 [running]:
internal/runtime/maps.fatal({0x10463300a?, 0x0?})
/opt/homebrew/opt/go/libexec/src/runtime/panic.go:1181 +0x20
main.main.func1(0x5c)
/Users/zhangjing/Desktop/interesting/test/test06.go:19 +0x5c
created by main.main in goroutine 1
/Users/zhangjing/Desktop/interesting/test/test06.go:17 +0xb0Go 的普通 map 写入时可能会修改 bucket、overflow bucket、扩容迁移状态等内部结构,这些操作不是原子的。多个 goroutine 同时写会破坏 map 内部结构的一致性,所以 runtime 会直接报 fatal error: concurrent map writes。