Go sync.pool的学习笔记中,有哪些或专业术语需要特别注意?
摘要:概述 sync.pool 对象池可以用来复用临时对象,减少内存压力,降低 GC 压力。 示例 基本用法 type Worker struct{} func (w *Worker) Name() string { return &am
概述
sync.pool 对象池可以用来复用临时对象,减少内存压力,降低 GC 压力。
示例
基本用法
type Worker struct{}
func (w *Worker) Name() string {
return "worker"
}
func main() {
workerPool := sync.Pool{New: func() interface{} {
return Worker{}
}}
worker := workerPool.Get().(Worker)
defer workerPool.Put(worker)
name := worker.Name()
fmt.Println(name)
}
sync.pool 是单对象池,不是多对象池。基本使用方法是 Get 和 Put 方法,Get 用来从对象池中取对象,Put 用来将不用的对象放回对象池中。
适用场景
sync.pool 中的对象可能会被运行时回收。有可能在需要使用时对象被回收而重新创建。因此,sync.pool 适合存储高频创建,作用时间短的对象。比如以下场景:
JSON 处理:频繁分配的 []byte 切片;
Web 服务:HTTP 请求处理的缓冲区;
数据库操作:连接池的辅助工具;
Go sync.Pool 的陷阱与正确用法:从踩坑到最佳实践 这篇文章写的很好关于 sync.pool 的陷阱和正确用法,可以参考学习,这里就不赘述了。
性能测试
var globalBuf []byte
func BenchmarkAllocateWithoutPool(b *testing.B) {
for i := 0; i < b.N; i++ {
buf := make([]byte, 1024)
globalBuf = buf // 这里将 buf 赋值给 globalBuf,不然会内存逃逸
}
}
func BenchmarkAllocateWithPool(b *testing.B) {
pool := sync.Pool{New: func() interface{} { return make([]byte, 1024) }}
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf := pool.Get().([]byte)
pool.Put(buf)
}
}
测试结果:
go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: go-by-example/sync/pool
cpu: Apple M3
BenchmarkAllocateWithoutPool-8 8672882 137.2 ns/op 1024 B/op 1 allocs/op
BenchmarkAllocateWithPool-8 36728509 31.91 ns/op 24 B/op 1 allocs/op
PASS
ok go-by-example/sync/pool 3.047s
Go Benchmark的输出格式为:
BenchmarkName-GOMAXPROCS Iterations TimePerOp(ns/op) BytesPerOp(B/op) AllocsPerOp(allocs/op)
TimePerOp:单次操作耗时(纳秒),越小越快。
AllocsPerOp:单次操作的内存分配次数,越小对GC越友好。
BytesPerOp:单次操作分配的总字节数,越小内存效率越高。
可以看出,使用 sync.pool 对象池相比于不使用 sync.pool 的性能对比:
单次操作耗时占比:31.91 / 137.2 = 23.2%
单次操作分配内存:24/1024 = 2.3%
并发
我们进一步看并发场景下对象复用是什么情况。
非并发场景
首先看非并发场景对象复用情况。
