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 +0xb0

Go 的普通 map 写入时可能会修改 bucket、overflow bucket、扩容迁移状态等内部结构,这些操作不是原子的。多个 goroutine 同时写会破坏 map 内部结构的一致性,所以 runtime 会直接报 fatal error: concurrent map writes。

comments如果有不同意见或者补充,直接留在这里。
contact

在别处继续找到我

如果你想聊技术、设计,或者只是打个招呼。

暂未配置外部链接