真实世界的Go设计模式 - 对象池模式

对象池(object pool pattern)是一种设计模式。一个对象池包含一组已经初始化过且可以使用的对象,而可以在有需求时创建和销毁对象。池的用户可以从池子中取得对象,对其进行操作处理,并在不需要时归还给池子而非直接销毁它。这是一种特殊的工厂对象。

若初始化、实例化的代价高,且有需求需要经常实例化,但每次实例化的数量较少的情况下,使用对象池可以获得显著的效能提升。从池子中取得对象的时间是可预测的,但新建一个实例所需的时间是不确定。

另外,利用对象池,我们可以重用对象,减少对象的分配,对于垃圾回收的编程语言,也是一种提高性能的手段。

sync.Pool是Go标准库sync包中的一个非常有用的结构,它可以用来管理和重用临时对象,以减少对象频繁分配和回收的开销。

sync.Pool的主要特点包括:

  • Pool维护一个可存储和共享临时对象的集合,这些对象无需频繁地申请和回收内存。
  • Pool通过New字段指定一个函数,用来生成新对象放入pool中。
  • 通过Get方法可以从pool中获得一个可重用对象,使用后调用Put方法将其放回pool。
  • 对象在不再需要时不会立即销毁,而是被保留在Pool中以便后续重用。
  • Pool会根据需求自动调整对象个数,会定期GC不再使用的对象。
  • 每个P都有一个本地pool,goroutine优先从本地pool获取和放回对象。

通过重用对象,可以减少内存分配和垃圾回收的开销,特别适合用于管理许多临时对象的场景,如处理大量并发请求时的缓冲、连接等。

比如net/rpc包就使用了链表来管理常用的Response、Request等对象的重用。

Go标准库database/sql包实现了一个连接池,可以重用数据库连接。使用DB.Conn()获取连接对象,操作完成后调用conn.Close()将其放回池中。该连接池默认最大空闲连接数2,可以通过SetMaxIdleConns进行调整。

fatih/pool 也是一个常用的网络连接池。

还有一类是goroutine pool,也又叫做worker pool的,这类的库就很多的,而且还有人源源不断的造轮子。比如常用的比如:

因为使用channel + goroutine很容易实现goroutine pool,所以也有很多关注度不高的轮子,都是作者针对自己的需求定制和改造的。使用也很方便。

Go标准库还有一个实验性的包arena,本来就在Go 1.20中就要推出的,但是在实现的过程中发现有问题,就一直没有暴露出来。proposal: arena: new package providing memory arenas 这个提案详细介绍了内容。你也可以把它看成是一个内存池,从其中产生的对象不会被垃圾回收掉,能够提升程序的性能。