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 缓存失效,需要加锁,冲突变多,性能急剧下降。
你学废了么?