go 语言中 map 默认是并发不安全的。

为什么要这么设计呢?

这就是矛与盾的关系,go 语言的设计者认为,在大部分场景中,对 map 的操作都非线程安全的;

我们不可能为了那小部分的需求,而牺牲大部分人的性能。

所以如果我们要使用线程安全的 map 的话,就需要做一些调整了。

那 go 语言中我们要使用线程安全的 map,该怎么操作呢?

非线程安全 map 的 panic

首先我们先来看下,map 如果不注意线程安全会报什么错!

直接上一个代码:

这里我瞬间起了 100 个 协程去写值,然后又瞬间起 100 个协程去读里面的值。

因为是异步协程,所以 map 同一时刻就可能被多个写的协程操作。

fatal error: concurrent map writes

方式一、加读写锁

要解决线程安全问题,第一个方案就是给他家读写锁。

读取的时候加读锁,写的时候加写锁。

当然如果你想加互斥锁也是可以的,只要上锁就好了。

这里提供一个 demo 代码:

方式二、使用官方的 sync.Map

go 语言官方其实也为我们提供了一个线程安全的 map,就在 sync 包里面。

用他里面的 map 也是线程安全的。

这个 map 使用起来,就没有基础的 map 方便了,写值的时候得通过 Store 方法,读的时候使用方法 Load 来读取。

当然还有其他API,我放一个截图:

下面我也提供一个使用 demo 代码:

两个方案的差异

我们先来看下 sync.Map 的结构体:

sync.Map

和我们的第一种方案 map+RWMutex 的实现并发的方式相比,减少了加锁对性能的影响。

它做了一些优化:可以无锁访问read map,而且会优先操作read map,倘若只操作read map就可以满足要求,那就不用去操作write map(dirty)。

所以在某些特定场景中它发生锁竞争的频率会远远小于 map+RWMutex 的实现方式

这样做的优点是:适合读多写少的场景;

缺点也就是:如果是写多的场景,会导致 read map 缓存失效,需要加锁,冲突变多,性能急剧下降。

你学废了么?