go之sync.Map

ZhDavis · · 2374 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

背景介绍:

在golang中map不是并发安全的,所有才有了sync.Map的实现,尽管sync.Map的引入确实从性能上面解决了map的并发安全问题,不过sync.Map却没有实现len()函数,这导致了在使用sync.Map的时候,一旦需要计算长度,就比较麻烦,一定要在Range函数中去计算长度(备注:这个后面会有例子给出)。

基于上面的现状,笔者从下面几点开始整理了这些知识:

  1. 普通map的介绍,包括线程不安全部分和常规使用方法。

  2. sync.Map的使用方式。

  3. sync.Map的底层实现介绍。

一、map的介绍

  1. 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 mainimport ( "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 mainimport ( "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

公众号:灰子学技术


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2374 次点击  ∙  1 赞  
加入收藏 微博
被以下专栏收入,发现更多相似内容
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传