灰子的Go笔记:sync.Map

时间:2022-07-23
本文章向大家介绍灰子的Go笔记:sync.Map,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

背景介绍:

在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 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