对于一些对象,为了优化性能,节省每次使用都创建新对象所带来的内存开销,可以使用池化技术,预先创建好一些对象放入池中,使用时从池中获取,使用完再放回池中。这样就减少了对象创建所带来的开销。
在go中,原生的池化数据结构为sync.Pool, 有三个方法:
- New字段为一个方法,定义为
func() interface{}
, 在新建Pool时,定义好New字段,以供后续从池中获取对象时,如果当前池中无对象,则使用此方法来新建对象。 - Get()方法:从池中获取对象
- Put(x interface{})方法:将对象放入池中
使用样例:
conPool := sync.Pool{
New: func() interface{} { return &Con{age: 20} },
}
con := conPool.Get().(*Con)
fmt.Printf("con.age: %v\n", con.Age())
conPool.Put(con)
sync.Pool用于保存一组可独立访问的临时对象,注意加粗的临时,说明它池化的对象在未来某个时候会被
毫无征兆的移除掉。
go中sync.Pool的特点:
- 池中对象数量没有最少最多限制,只要存和取,都可以成功
- 池中的对象会被随机回收,当程序GC时,可能会回收池中对象
- 存和取可以并发使用,本身就是线程安全的
- 池中的对象是bytes.Buffer时,如果buffer的cap随着每次使用而变化,尽管每次使用完后buffer.Reset()清空了长度,但底层的cap仍然很大,会造成内存泄露
使用sync.Pool的注意点:
- 因为池化对象可能会被垃圾回收,因此对于tcp链接、数据库链接等一下长连接是不合适的
- 如果要限制池中对象的最多最少限制,需要自己在这基础上做修改
- 如果池中对象有bytes.Buffer类型, 每次放回池之前对cap做判断,如果太大就不再放回池中,避免内存泄露
连接池
Pool 的另一个很常用的一个场景就是保持 TCP 的连接。
(事实上,很少用sync.Pool去池化连接对象,因为sync.Pool会无通知的将某个连接回收,因此
会其他方法来池化对象)
- 标准库中的 http client 池
http.Client 实现连接池的代码是在 Transport 类型中,它使用 idleConn 保存持久化的可重用的长连接: - fatih/pool的tcp连接池
它的 Pool 是通过 Channel 实现的,空闲的连接放入到 Channel 中,这也是 Channel 的一个应用场景: - 数据库连接池
通过 MaxOpenConns 和 MaxIdleConns 控制最大的连接数和最大的 idle 的连接数。 - Memcached Client 连接池
gomemcache是他使用 Go 开发的 Memchaced 的客户端,其中也用了连接池的方式池化 Memcached 的连接
Worker Pool
当使用并发任务时,我们常常建立一个任务池,控制并发时最少最多的goroutine数量。
总结
Pool是一个通用的概念,当程序中有很多需要重复创建的对象,可以创建池类对象。