map不是并发安全的 官方的faq 里有说明,考虑到有性能损失,map没有设计成原子操作,在并发读写时会有问题。
Map access is unsafe only when updates are occurring. As long as all goroutines are only reading—looking up elements in the map, including iterating through it using a for range loop—and not changing the map by assigning to elements or doing deletions, it is safe for them to access the map concurrently without synchronization.
查看源码,进一步立即实现机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const ( ... hashWriting = 4 ... ) type hmap struct { ... flags uint8 ... } map 是检查是否有另外线程修改h.flag来判断,是否有并发问题。 if h.flags&hashWriting == 0 { throw("concurrent map writes" ) } if h.flags&hashWriting != 0 { throw("concurrent map read and map write" ) }
测试并发问题的例子:一个goroutine不停地写,另一个goroutine不停地读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "time" ) func main () { c := make (map [string ]int ) go func () { for j := 0 ; j < 1000000 ; j++ { c[fmt.Sprintf("%d" , j)] = j } }() go func () { for j := 0 ; j < 1000000 ; j++ { fmt.Println(c[fmt.Sprintf("%d" , j)]) } }() time.Sleep(time.Second * 20 ) }
立马产生错误
1 2 3 4 5 6 7 8 9 0 fatal error: concurrent map read and map write goroutine 19 [running]: runtime .throw (0 x10d6ea4, 0 x21) /usr/ local/go/ src/runtime/ panic.go:774 +0 x72 fp=0 xc00009aef0 sp=0 xc00009aec0 pc=0 x10299c2 runtime .mapaccess1_faststr(0 x10b41e0, 0 xc000066180, 0 x116ae71, 0 x1, 0 x1) /usr/ local/go/ src/runtime/m ap_faststr.go:21 +0 x44f fp=0 xc00009af60 sp=0 xc00009aef0 pc=0 x100ffff main.main.func2(0 xc000066180)
This statement declares a counter
variable that is an anonymous struct containing a map and an embedded sync.RWMutex
.
1 2 3 4 var counter = struct { sync.RWMutex m map [string ]int }{m: make (map [string ]int )}
To read from the counter, take the read lock:
1 2 3 4 counter.RLock () n := counter.m ["some_key" ] counter.RUnlock () fmt.Println ("some_key:" , n)
To write to the counter, take the write lock:
1 2 3 counter.Lock () counter.m ["some_key" ] ++ counter.Unlock ()
针对上面有并发问题的测试例子,可以修改成以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" "sync" "time" ) func main () { var c = struct { sync.RWMutex m map [string ]int }{m: make (map [string ]int )} go func () { for j := 0 ; j < 1000000 ; j++ { c.Lock() c.m[fmt.Sprintf("%d" , j)] = j c.Unlock() } }() go func () { for j := 0 ; j < 1000000 ; j++ { c.RLock() fmt.Println(c.m[fmt.Sprintf("%d" , j)]) c.RUnlock() } }() time.Sleep(time.Second * 20 ) }
sync.Map sync.Map 是官方出品的并发安全的 map,他在内部使用了大量的原子操作来存取键和值,并使用了 read 和 dirty 二个原生 map 作为存储介质,具体实现流程可阅读相关源码。
参考:
https://learnku.com/articles/27691
参考链接
The Go Blog - Go maps in action
Why are map operations not defined to be atomic?