概述 #
sync.Pool 用来复用对象,减少内存分配,降低 GC 压力。
特性 #
sync.Pool的大小可伸缩,高负载时会动态扩容,池中的对象在不活跃时会被自动清理。
如何使用 #
只需实现 sync.Pool 对象的 New 方法即可,当对象池中没有对象时,将会调用自定义的 New 方法创建。
package main
import (
	"sync"
)
type person struct {
	name string
	age  int
}
var (
	// 实现 New 方法
	personPool = sync.Pool{
		New: func() interface{} {
			return new(person)
		},
	}
)
func main() {
	// Get 方法从池中申请一个对象
	// 因为返回值是 interface{}, 这里再加一个类型转换
	tom := personPool.Get().(*person)
	tom.name = "Tom"
	tom.age = 6
	println(tom.name, tom.age)
	// Put 方法将对象归还到池中
	personPool.Put(tom)
}
普通方法 #
测试代码如下:
package performance
import (
	"bytes"
	"testing"
)
func BenchmarkBufferWithPool(b *testing.B) {
	data := make([]byte, 1024)
	for n := 0; n < b.N; n++ {
		// 每次初始化一个新对象
		var buf bytes.Buffer
		buf.Write(data)
		buf.Reset()
	}
}
运行测试,并将基准测试结果写入文件:
# 运行 1000 次,统计内存分配
$ go test -run='^$' -bench=. -count=1 -benchtime=1000x -benchmem > slow.txt
对象池 #
测试代码如下:
package performance
import (
	"bytes"
	"sync"
	"testing"
)
var bufferPool = sync.Pool{
	New: func() interface{} {
		return &bytes.Buffer{}
	},
}
func BenchmarkBufferWithPool(b *testing.B) {
	data := make([]byte, 1024)
	for n := 0; n < b.N; n++ {
		// 对象复用,从对象池中获取对象
		buf := bufferPool.Get().(*bytes.Buffer)
		buf.Write(data)
		buf.Reset()
		bufferPool.Put(buf)
	}
}
运行测试,并将基准测试结果写入文件:
# 运行 1000 次,统计内存分配
$ go test -run='^$' -bench=. -count=1 -benchtime=1000x -benchmem > fast.txt
使用 benchstat 比较差异 #
$ benchstat -alpha=100 fast.txt slow.txt 
# 输出如下:
name              old time/op    new time/op    delta
BufferWithPool-8    75.9ns ± 0%   500.0ns ± 0%    +558.94%  (p=1.000 n=1+1)
name              old alloc/op   new alloc/op   delta
BufferWithPool-8     2.00B ± 0%  1024.00B ± 0%  +51100.00%  (p=1.000 n=1+1)
name              old allocs/op  new allocs/op  delta
BufferWithPool-8      0.00           1.00 ± 0%       +Inf%  (p=1.000 n=1+1)
输出的结果分为了三行,分别对应基准期间的: 运行时间、内存分配总量、内存分配次数,采用了 复用池 方案后:
- 运行时间提升了 
5 倍+ - 内存分配总量降低了 
500 倍+ - 内存分配次数降至 0
 
因为时间关系,基准测试只运行了 1000 次,运行次数越大,优化的效果越明显。感兴趣的读者可以将 -benchtime 调大后看看优化效果。
性能分析 #
普通方法 每次重新申请一个新的 bytes.Buffer 对象,用完之后再释放掉,大部分时间浪费在了申请资源和释放资源上面,
而 对象池 通过资源池复用了 bytes.Buffer 对象,避免了申请资源和释放资源的时间损耗,所以性能远高于 普通方法。
使用建议 #
sync.Pool有自动清除对象机制,因此重要 (不可改变) 的对象不要使用sync.Pool- 从 
sync.Pool获取到的对象状态可能不同,例如长度会发生变化的数据类型 (如切片) - 从 
sync.Pool获取到的对象使用完成后,要及时调用Put归还 
小结 #
本小节对 普通方法 申请对象和 对象池 申请对象两种方法进行基准测试,并比较了两者在性能和内存方面的差异。
从结果中可以看到,对象池 所带来的性能提升是非常大的,这也提示我们,在申请和释放大的资源对象 (如数据库连接, 复杂的结构体)时, 可以使用 sync.Pool 来优化提升性能。