背景介绍:
在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
今天的文章灰子的Go笔记:sync.Map分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/66288.html