golang中的map不是并发安全的,并发对map读写可能会有问题,如:

// N太小时不会(比如10),因机器而异
// fatal error: concurrent map read and map write
func mapDemo1() {
	m := make(map[string]int)

	go func() {
		for i := 0; i < N; i++ {
			m[strconv.Itoa(i)] = i // write
		}
	}()

	go func() {
		for i := 0; i < N; i++ {
			fmt.Println(i, m[strconv.Itoa(i)]) // read
		}
	}()

	time.Sleep(time.Second * 5)
}

上面的代码, 开了两个goroutine,一个对map写,一个对map读。
在N较小时(应该要因机器而异,不是太明确,测试时N=10不会panic),在N较大时(比如1000),就会报错:

fatal error: concurrent map read and map write

意思就是map并发读写是有问题的,会panic掉。

那么怎么办呢?
在go1.9以前,可以加锁实现:

type Cmap struct {
	m map[string]int
	//lock *sync.Mutex
	lock *sync.RWMutex
}

func (c *Cmap) Get(key string) int {
	c.lock.RLock()
	defer c.lock.RUnlock()
	return c.m[key]
}

func (c *Cmap) Set(key string, val int) {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.m[key] = val
}

这样的话,Cmap就是自身带有锁的map,在读和写的位置加锁就可以:

func mapDemo2() {
	m := make(map[string]int)
	//lock := new(sync.Mutex)
	lock := new(sync.RWMutex)
	cm := Cmap{
		m:    m,
		lock: lock,
	}

	go func() {
		for i := 0; i < N; i++ {
			cm.Set(strconv.Itoa(i), i)
		}
	}()

	go func() {
		for i := 0; i < N; i++ {
			fmt.Println(i, cm.Get(strconv.Itoa(i)))
		}
	}()

	time.Sleep(time.Second * 5)
}

注意上述的两句注释,你可以用sync.Mutex (互斥锁),或者sync.RWMutex(读写锁, 支持多个读锁),对上述代码实际运行结果不影响。

sync.Map
func mapDemo3() {
	var m sync.Map

	go func() {
		for i := 0; i < N; i++ {
			m.Store(strconv.Itoa(i), i) // 写
		}
	}()

	go func() {
		for i := 0; i < N; i++ {
			v, _ := m.Load(strconv.Itoa(i)) // 读
			fmt.Println(i, v)
		}
	}()

	time.Sleep(time.Second * 5)
}

可以看到Store就是写,Load就是读。
还有几个方法,说明如下:

var m sync.Map

actual, loaded := m.LoadOrStore("k1", "v1")
fmt.Println(actual, loaded) // v1 false

actual, loaded = m.LoadOrStore("k1", "v1")
fmt.Println(actual, loaded) // v1 true

actual, loaded = m.LoadOrStore("k1", "v2")
fmt.Println(actual, loaded) // // v1 true

actual, loaded = m.LoadOrStore("k2", "v2")
fmt.Println(actual, loaded) // v2 false

欢迎补充指正!