sync.Map存的是值
Slice是引用类型
sync.Pool是引用类型
sync.Map存的是值
1 | func main() { |
模拟多线程修改从sync.Map中Load的值,修改是隔离的,aa未被修改。对于Struct也是如此:
1 | func main() { |
Slice是引用类型
1 | func main() { |
在不同线程中修改从sync.Map中Load的Slice,修改是非隔离的。例如在其他线程修改了bb,aa也会受影响
sync.Pool是引用类型
1 | func changePool(pool sync.Pool) { |
在changePool中取出元素,在main函数中再次Get,会调用make生成元素
在sync.Pool中存Slice对象还是地址?
我们来测试在一个sync.Pool中存Slice对象地址
1 | func TestOnlyCheckIn() { |
可以看到,假如b线程从sync.Pool里取出地址并修改对象的值,a线程先前存入的对象会被修改。假如存入地址的对象还在使用,就会出现问题。再次放回sync.Map里也是如此
1 | func TestCheckIntoMapAndCheckOut() { |
那么sync.Pool直接存Slice对象行不行呢?我们来看一下
1 | func TestSafeWayToUseSyncPool() { |
并没有和我们想的一样,b的修改不影响a。因为Slice是引用类型,除非是deepCopy,否则对Slice修改,都是修改其内容。这就很危险了,不过要手动回收垃圾,这倒是简便的写法。
确保Slice不会再次使用时,把它放到sync.Pool里,再次取出时直接往Slice里写,这时候是写入老的内存地址中。
有一个隐藏的严重问题。sync.Pool有没有可能是单例?我在sync.Map里存多个sync.Pool是否有效?下面测试一下。
测试能否建立多个互不影响的sync.Pool
1 | func TestMutiSyncPool() { |
可喜可贺,sync.Pool支持多个且互不影响。我们可以把不同长度的Person Slice存到一个sync.Map以充分利用内存。
这里其实还忽视了一个隐藏的问题。虽然sync.Pool是引用类型,作为sync.Map值时却不是传引用。下面验证一下
1 | func TestSafeWayToUseSyncPoolAndMap() { |
悲剧,如果不把Pool存回Map,在外面对Pool的修改是不生效的,如果Pool在Map里存的是地址呢?我们再看一下。
1 | func TestNotPutPoolIntoMapButUsePtr() { |
真棒!sync.Pool可以在各线程中共享值而不必放回sync.Map!
sync.Pool收集内存的正确使用方式
来走一遍收集内存的流程
1 | type Person struct { |
写对不容易,忽略一堆魔鬼变量取名T T。精简一下,得到最终测试代码。
1 | type Person struct { |
每次系统GC,会带走所有sync.Pool中的元素。下面是第一版错误的例子23333
1 | type Person struct { |
Pool里存对象地址,在第一轮append的时候,slice2的内容变成了长度4的Slice,导致slice1拿到的newSlice出错。并且对newSlice修改之后,还会改动slice2的内容。这是非常严重的运行时错误。