go之sync.Map

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

背景介绍:

在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

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