背景介绍:
在golang中map不是并发安全的,所有才有了sync.Map的实现,尽管sync.Map的引入确实从性能上面解决了map的并发安全问题,不过sync.Map却没有实现len()函数,这导致了在使用sync.Map的时候,一旦需要计算长度,就比较麻烦,一定要在Range函数中去计算长度(备注:这个后面会有例子给出)。
基于上面的现状,笔者从下面几点开始整理了这些知识:
-
普通map的介绍,包括线程不安全部分和常规使用方法。
-
sync.Map的使用方式。
-
sync.Map的底层实现介绍。
一、map的介绍
-
map的并发不安全
例子如下:
package main
import (
"fmt"
"sync"
)
func main() {
test := map[int]int{}
var wg sync.WaitGroup
i := 0
for i < 1000 {
wg.Add(1)
go func() {
defer wg.Done()
test[1] = i
}()
i++
}
wg.Wait()
fmt.Println(test)
}
输出结果: 通过运行结果我们可以看出map是并发不安全的。
2. map的常规使用
基于上面的原因,我们在工程里面使用map操作的时候,通常会添加互斥锁或者读写锁来解决map的并发不安全问题。
例子如下:
package main
import (
"fmt"
"sync"
)
func main() {
test := map[int]int{}
var wg sync.WaitGroup
var l sync.Mutex
i := 0
for i < 1000 {
wg.Add(1)
go func() {
defer wg.Done()
l.Lock()
test[1] = i
l.Unlock()
}()
i++
}
wg.Wait()
fmt.Println(test)
}
输出结果:添加互斥锁之后,map便可以解决并发冲突问题。
map[1:1000]
不好之处:
同样的道理读写锁也可以达到上面的效果,虽然这样map可以在工程里面使用,但是效果不好,毕竟加锁的话,就是对整个map进行加锁和解锁,导致整个map在加锁的过程中都被阻塞住,这种操作会严重影响性能。
二、sync.Map的使用
也正是因为上面的原因,golang官方提供了一个官方的sync.map来解决这个问题,具体api如下所示:
对于sync.Map来说,他并没有实现len()函数,不过他却提供了一个Range函数,用于我们来计算sync.Map的长度。
先看一个解决并发问题的例子:
package main
import (
"fmt"
"sync"
)
func main() {
test := sync.Map{}
var wg sync.WaitGroup
i := 0
for i < 1000 {
wg.Add(1)
go func() {
defer wg.Done()
test.Store(1,i)
}()
i++
}
wg.Wait()
fmt.Println(test.Load(1))
}
结果输出:
map[1:1000]
例子2, 计算sync.Map的长度
package main
import (
"fmt"
"sync"
)
func main() {
test := sync.Map{}
var wg sync.WaitGroup
i := 0
for i < 1000 {
wg.Add(1)
go func(i int) {
defer wg.Done()
test.LoadOrStore(i, 1)
}(i)
i++
}
wg.Wait()
len := 0
test.Range(func(k, v interface{}) bool {
len++
return true
})
fmt.Println("len of test:",len)
}
输出结果:
len of test: 1000
三、sync.Map的底层实现介绍
map的源代码可以参考下面的链接:https://golang.org/src/sync/map.go?s=1149:2596#L17
pe Map struct { 28 mu Mutex 29 30 // read contains the portion of the map's contents that are safe for 31 // concurrent access (with or without mu held). 32 // 33 // The read field itself is always safe to load, but must only be stored with 34 // mu held. 35 // 36 // Entries stored in read may be updated concurrently without mu, but updating 37 // a previously-expunged entry requires that the entry be copied to the dirty 38 // map and unexpunged with mu held. 39 read atomic.Value // readOnly 40 41 // dirty contains the portion of the map's contents that require mu to be 42 // held. To ensure that the dirty map can be promoted to the read map quickly, 43 // it also includes all of the non-expunged entries in the read map. 44 // 45 // Expunged entries are not stored in the dirty map. An expunged entry in the 46 // clean map must be unexpunged and added to the dirty map before a new value 47 // can be stored to it. 48 // 49 // If the dirty map is nil, the next write to the map will initialize it by 50 // making a shallow copy of the clean map, omitting stale entries. 51 dirty map[interface{}]*entry 52 53 // misses counts the number of loads since the read map was last updated that 54 // needed to lock mu to determine whether the key was present. 55 // 56 // Once enough misses have occurred to cover the cost of copying the dirty 57 // map, the dirty map will be promoted to the read map (in the unamended 58 // state) and the next store to the map will make a new dirty copy. 59 misses int
60 }
type readOnly struct { 64 m map[interface{}]*entry 65 amended bool // true if the dirty map contains some key not in m. 66 }
对于sync.Map的英文描述如下所示:
// Map is like a Go map[interface{}]interface{} but is safe for concurrent use 13 // by multiple goroutines without additional locking or coordination. 14 // Loads, stores, and deletes run in amortized constant time. 15 // 16 // The Map type is specialized. Most code should use a plain Go map instead, 17 // with separate locking or coordination, for better type safety and to make it 18 // easier to maintain other invariants along with the map content. 19 // 20 // The Map type is optimized for two common use cases: (1) when the entry for a given 21 // key is only ever written once but read many times, as in caches that only grow, 22 // or (2) when multiple goroutines read, write, and overwrite entries for disjoint 23 // sets of keys. In these two cases, use of a Map may significantly reduce lock 24 // contention compared to a Go map paired with a separate Mutex or RWMutex. 25 // 26 // The zero Map is empty and ready for use. A Map must not be copied after first
对于sync.Map有两个map构成,一个用于读,一个用于写。用于写的叫dirty,采用互斥锁进行加锁,对于只读的数据会先读提供读的map,然后才会去读dirty。
为了优化sync.Map的性能,还提供了一个missed计数,用于来决策何时将dirty中的元素变成只读的map元素等操作。
具体实现细节可以参考代码,或者 参考文档中的3.
参考文档:
1. https://golang.org/pkg/sync/#Map
2. https://blog.csdn.net/u011957758/article/details/82846609
3. 由浅入深聊聊Golang的sync.Map:
https://juejin.im/post/6844903895227957262
原文:https://mp.weixin.qq.com/s/rZJnMA0CyiVQPNYq1JQh1w
公众号:灰子学技术
有疑问加站长微信联系(非本文作者)