sync.Pool
# 介绍
sync.Pool 数据类型用来保存一组可独立访问的临时对象。请注意这里加粗的“临时”这两个字,它说明了sync.Pool这个数据类型的特点,也就是说,它池化的对象会在未来的某个时候被毫无预兆的移除掉。 因为Pool可以有效的减少新对象的申请,从而提高程序性能,所以Go内部库也用到了sync.Pool,比如fmt包,它会使用一个动态大小的buffer池做输出缓存。
- 1.sync.Pool 本身就是线程安全的,多个goroutine可以并发地调用它的方法存储对象;
- 2.sync.Pool 不可以在使用之后再复制使用。
# sync.Pool的使用方法
提供了三个对外的方法:New、Get和Put。
- 1.New Pool struct包含一个New字段,这个字段的类型是函数func() interface{}。当调用Pool的Get方法从池中获取元素,没有更多的空闲元素可返回时,就会调用这个New方法来创建新的元素。如果你没有设置New字段,没有更多的空间元素可返回时,Get方法将返回nil,表明当前没有可用的元素。
- 2.Get 如果调用这个方法,就会从Pool取走一个元素,这也就意味着,这个元素会从Pool中移除,返回给调用者。不过,除了返回值是正常实例化的元素,Get方法的返回值还可能会是一个nil(Pool.New字段没有设置,又没有空闲元素可以返回),所以你在使用的时候,可能需要判断。
- 3.Put
这个方法用于将一个元素返还给Pool,Pool会把这个元素保存到池中,并且可以复用。但如果Put一个nil值,Pool就会忽略这个值。
下面我们看看sync.Pool最常用的一个场景:buffer池(缓冲池)。
var buffers = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func GetBuffer() *bytes.Buffer {
return buffers.Get().(*bytes.Buffer)
}
func PutBuffer(buf *bytes.Buffer) {
buf.Reset()
buffers.Put(buf)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
注意点,内存泄漏和内存浪费。
- 内存泄漏:取出来的bytes.Buffer在使用的时候,我们可以往这个元素中增加大量的byte数据,这会导致底层的byte slice的容量可能会变得很大。这个时候,即使Reset再放回到池子中,这些byte slice的容量不会改变,所占的空间依然很大。而且,因为Pool回收的机制,这些大的Buffer可能不被回收,而是一直占用很大的空间,这属于内存泄漏问题。 解决方法:元素在放回时,增加了检查逻辑,改成放回的超过一定大小的buffer,就直接丢弃掉,不再放入池子中。
- 内存浪费:池子中的buffer都比较大,但在实际使用的时候,很多时候只需要一个小的buffer,这也是一种浪费现象。 解决方法:可以将buffer池分成几层。
var (
bufioWriter2kPool sync.Pool
bufioWriter4kPool sync.Pool
)
func bufioWriterPool(size int) *sync.Pool {
switch size {
case 2 << 10:
return &bufioWriter2kPool
}
case 4 << 10:
return &bufioWriter4kPool
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
上次更新: 2023/06/05