funcmain() { var m = make(map[int]int) var wg sync.WaitGroup wg.Add(2) // 启动两个协程序同时写入 map gofunc() { for i := 0; i < 100000; i++ { m[i] = i // 写入 } wg.Done() }() gofunc() { for i := 0; i < 100000; i++ { m[i] = i // 写入 } wg.Done() }() wg.Wait() }
运行便会报错:fatal error: concurrent map writes
并发读写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
funcmain() { var m = make(map[int]int) var wg sync.WaitGroup wg.Add(2) // 启动两个协程序同时写入 map gofunc() { for i := 0; i < 100000; i++ { m[i] = i // 写入 } wg.Done() }() gofunc() { for i := 0; i < 100000; i++ { fmt.Println(m[i]) // 读取 } wg.Done() }() wg.Wait() }
运行会报错:fatal error: concurrent map read and map write
为什么go语言的map允许并发读写呢?
这跟 map 的实现有关,根本原因是:map 的底层是支持自动扩容的,在添加元素的时候,如果发现容量不够,就会自动扩容。 如果允许扩容和访问操作同时发生,那么访问到的数据就不一定就是我们之前存放进去的了,所以 Go 从设计上就禁止了这种操作。 也就是 fail fast 的原则。
// 合并部分结果 gofunc() { for partIndex := range channel { // 锁定访问共享的 map for key, value := range partIndex { if _, ok := indexStrings[key]; !ok { indexStrings[key] = value } else { indexStrings[key] += "-" + value } } } }()
// 启动多个goroutine来计算部分结果 for i := 0; i < n; i++ { wg.Add(1) gofunc() { defer wg.Done() indexString := integrateInvertedIndexString() if indexString != nil { channel <- indexString } }() }