8.2.6 sync.Map
如果要缓存一部分体量不大的数据,完全可以考虑使用sync.Map。Go语言在1.9以上的版本的标准包就提供了该功能。
在1.6版本以前,Go语言自带的标准的map类型是并发读安全的,但是并发写不安全。所以1.6版本以前需要自己写一个并发读写安全的map,不过,实现这个map也非常简单,只需要定义一个带有RWMutex锁和map类型的结构体即可,示例如下:
var safeMap = struct{ sync.RWMutex m map[string]int }{m:make(map[string]int)}
上面的代码非常简便地定义了变量safeMap,使用的时候也非常简单,如下所示:
safeMap.RLock() n := safeMap.m["key"] safeMap.RUnlock()
上面的代码实现了map的读安全,而下面的代码实现了写安全:
safeMap.Lock() safeMap.m["key"]=1 safeMap.Unlock()
可见之前的用法也不算复杂,但是这种实现方式在大并发写的情况下会有性能问题。也就是说在写操作时,上述实现方式相当于串行写,非常容易发生性能问题。所以,Go语言提供了标准的sync.Map。
sync.Map在并发安全性上的实现是典型的空间换时间的思想,其提供了read和dirty这两个数据结构,用来降低加锁对性能的负面影响。sync.Map的结构体定义如下:
type Map struct{ mu Mutex //dirty操作使用该锁 read atomic.Value //只读数据结构 dirty map[interface{}]*entry //包含当前Map的entry misses int //read内没有而dirty内有的entry数 }
read变量是只读的,因此不存在并发安全的问题。实际上,read的结构体内是包含entry的,且entry是会更新的。如果entry在Map内没有被删除则不需要加锁,反之则需要加锁,以便更新dirty的数据。
dirty含有最新的写入数据,并且在写操作执行时会把read中未删除的数据复制到dirty中,dirty的操作需要mu锁。
从read内读数据的时候,如果read内没有数据,会到dirty内读取,然后misses会加1。待misses的数值等于dirty的长度时,会把dirty数据提升到read内。
上面对于原理的介绍可能还是有读者不能透彻理解,因为篇幅原因本书中不做详细的源码分析,请有兴趣的读者分析sync.Map的源码,结合上述介绍,就可以理解其实现。
sync.Map提供的常用方法有如下五个。
▪Load(key interface{}) (value interface{},ok bool):通过参数key查询对应的value,如果不存在则返回nil;ok表示是否找到对应的值。
▪Store(key,value interface{}):该方法相当于对sync.Map的更新或新增,参数是键值对。
▪LoadOrStore(key,value interface{}) (actual interface{},loaded bool):该方法的参数为key和value。该方法会先根据参数key查找对应的value,如果找到则不修改原来的值并通过actual返回,并且loaded为true;如果通过key无法查找到对应的value,则存储key-value并且将存储的value通过actual返回,loaded为false。
▪Delete(key interface{}):通过key删除键值对。
▪Range(f func(key,value interface{}) bool):遍历sync.Map的元素,注意for...range map是对内置map类型的用法,sync.Map需要使用单独的Range方法。
下面写一段简单的示例代码,介绍sync.Map的使用方法:
book/ch08/8.2/map/map.go
1. package main
2.
3. import (
4. "fmt"
5. "sync"
6. )
7.
8. var m sync.Map
9.
10. func main() {
11. //新增
12. m.Store(1,"one")
13. m.Store(2,"two")
14. //LoadOrStore key不存在
15. v,ok := m.LoadOrStore(3,"three")
16. fmt.Println(v,ok)//three false
17. //LoadOrStore key存在
18. v,ok = m.LoadOrStore(1,"thisOne")
19. fmt.Println(v,ok)//one true
20. //Load
21. v,ok = m.Load(1)
22. if ok{
23. fmt.Println("key is existed,and value is:",v)
24. }else{
25. fmt.Println("key is not existed")
26. }
27. //Range
28. //先为Range准备一个传入的函数,该函数的声明格式是固定的,只有内部代码
29. //可以自定义
30. f := func(k,v interface{}) bool{
31. fmt.Println(k,v)
32. return true
33. }
34. //执行Range
35. m.Range(f)
36.
37. //Delete
38. m.Delete(2)
39. fmt.Println(m.Load(2)) //nil false
40. }
示例代码非常简单,而且也加了注释,此处就不再做详细介绍了。
sync.Map在实战中经常用到。在存储的内存不是很多的情况下,完全可以使用sync.Map,性能非常优异,而且并非所有的场景都优先考虑redis(一款常用的key-value远程存储服务)。