HOME
BLOG
FOOTPRINTS
RSS
golang sync.Map源码解析
Mar 01 2019

golang的内建类型map是非线程安全的,当我们使用并发操作去写map时会引发panic

package main

import (
 "fmt"
 "sync"
)

func main() {
 var m = make(map[string]string,100)

 var wait  sync.WaitGroup

 for i := 0 ;i < 100 ; i ++ {
  wait.Add(1)
  go func() {
   m[fmt.Sprintf("%d",i)] = "test"
   wait.Done()
  }()
 }

 wait.Wait()
}
fatal error: concurrent map writes
fatal error: concurrent map writes

goroutine 11 [running]:
runtime.throw(0x10c6370, 0x15)
 /usr/local/go/src/runtime/panic.go:608 +0x72 fp=0xc00002cf08 sp=0xc00002ced8 pc=0x1026e52
runtime.mapassign_faststr(0x10a9860, 0xc00006e120, 0xc0000a0010, 0x2, 0x1)
 /usr/local/go/src/runtime/map_faststr.go:199 +0x3da fp=0xc00002cf70 sp=0xc00002cf08 pc=0x100fada
main.main.func1(0xc00006e120, 0xc000016078, 0xc000016080)
...

因此当我们需要并发修改map时,第一种方法是在每次修改map值时使用互斥锁,第二种则是直接使用golang内置的线程安全的map,也就是sync.Map。

本文则从sync.Map的源码层面介绍,sync.Map是如何实现线程安全的。

首先我们看下sync.Map的数据结构

// Map 类似于map[interface{}]interface{},但是对于多个goroutine并发使用是安全的,不需要额外的lock
// 
// 该类型是专门为并发操作提供的,你应该使用该类型来替代需要对原生map加锁的场景,
// 以此获得更好地安全性,并使得操作更加简便
//
// Map针对了两个常见的场景进行了优化(1)对一个key只写入以此,但是读很多次。(2)在并发场景下,goroutine不会
// 同时去操作一个key(也就是不会发生竞态)。
// 在这两种情况下使用Map会比使用RWMutex加锁的map效率更高
//
// 你可以直接var x sync.Map就能使用,不需要去执行New...,Map不能被copy
type Map struct {
 mu Mutex

    // read中保存了Map中部分内容,这些内容是只读的,所以是线程安全的
    // 其中保存的数据类型是readOnly
 read atomic.Value // readOnly

    // 所有对dirty的操作都是需要加锁的
    // 如果dirty为空,下一次写操作会复制read中没被删除的数据到dirty
 dirty map[interface{}]*entry

    // 当从Map中读取entry时,会先去read中读取,如果read中读不到则到dirty中读取,这是
    // 会将该值+1,当该值到达一定大小后就会将read中所有值更新为dirty中保存的值
 misses int
}

从Map的数据结构看,就不难发现Map是如何保证线程安全的

当往Map中添加新的值时,不会往read中插入数据,而是直接将数据保存在dirty中。当需要从Map中读取数据时,
会先从read中读取数据,如果读到直接返回,如果读不到那么就会从dirty中读取,并更新misses的值,当misses值到达一定数值之后,
就会将dirty的值赋给read。(所有对dirty的操作都是加锁的,这就保证了这个类是线程安全的,同时因为read-only的存在,提升了并发读取的效率)

// readOnly is an immutable struct stored atomically in the Map.read field.
type readOnly struct {
 m       map[interface{}]*entry
 amended bool // 如果dirty中存在一些m中没有的key,该值则为true
}
// An entry is a slot in the map corresponding to a particular key.
type entry struct {
 // p points to the interface{} value stored for the entry.
 //
    // p有三种情况
    // p == nil: entry已经被删除,且m.dirty为nil
    // expunged: entry已经被删除,但是m.dirty不是nil,并且这个entry不在m.dirty中
    // 其他: entry是个正常值
 p unsafe.Pointer // *interface{}
}

Load

Load方法用于根据Key读取Map中的值

// Load 根据所给的key返回Map中的值,如果不存在返回nil
//
// ok 表示Map中是否包含key
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    // 直接从readonly中读取,如果读到直接返回,因为是readonly所以不用加锁
 read, _ := m.read.Load().(readOnly)
 e, ok := read.m[key]
    // 如果readonly中没有值,并且dirty中存在read中不存在的值时
 if !ok && read.amended {
  m.mu.Lock()
        // 加锁,双检查
  read, _ = m.read.Load().(readOnly)
  e, ok = read.m[key]
        // 如果read中仍然不存在该key,且dirty中有read中不存在的值
  if !ok && read.amended {
            // 从dirty中检查是否有该key
   e, ok = m.dirty[key]
            // 不管dirty中是否有改key,都将misses+1
            // 当misses到达一定值之后,m.dirty会被提升为read
   m.missLocked()
  }
  m.mu.Unlock()
 }
 if !ok {
  return nil, false
 }
 return e.load()
}