sync.pool对象在并发比较高的系统中是非常常见的,这篇博客向大家介绍sync包创建可复用的实例池的原理以及使用方法,希望对你有帮助。1. 池的内部工作流程 我们调用sync.pool包中的方法的时候,init函数会自动注册一个清理pool对象的方法,该方法会在GC执行前被调用,所以Pool会在调用GC的时候性能较低(初始化的对象都被清理了,重新创建就会产生开销)。Pool只在两次GC之间是有效的,下面是官方的一张图,用来理解池的管理方式:
文章图片
对于我们创建的每个
sync.Pool
,go 生成一个连接到每个处理器(处理器即 Go 中调度模型 GMP 的 P,pool 里实际存储形式是 [P]poolLocal
)的内部池 poolLocal
。该结构由两个属性组成:private
能由其所有者访问(push 和 pop 不需要任何锁)shared
该属性可由任何其他处理器读取,并且需要并发安全。
2. 两个重要方法
- Get方法
// 1)尝试从本地P对应的那个本地池中获取一个对象值, 并从本地池冲删除该值。 // 2)如果获取失败,那么从共享池中获取, 并从共享队列中删除该值。 // 3)如果获取失败,那么从其他P的共享池中偷一个过来,并删除共享池中的该值(p.getSlow())。 // 4)如果仍然失败,那么直接通过New()分配一个返回值,注意这个分配的值不会被放入池中。New()返回用户注册的New函数的值,如果用户未注册New,那么返回nil。 func (p *Pool) Get() interface{} { if race.Enabled { race.Disable() } l := p.pin() x := l.private l.private = nil runtime_procUnpin() if x == nil { l.Lock() last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] } l.Unlock() if x == nil { x = p.getSlow() } } if race.Enabled { race.Enable() if x != nil { race.Acquire(poolRaceAddr(x)) } } if x == nil && p.New != nil { x = p.New() } return x }
- Put方法
// 将x添加到池里面 // 1)如果放入的值为空,直接return. // 2)检查当前goroutine的是否设置对象池私有值,如果没有则将x赋值给其私有成员,并将x设置为nil。 // 3)如果当前goroutine私有值已经被设置,那么将该值追加到共享列表。 func (p *Pool) Put(x interface{}) { if x == nil { return } if race.Enabled { if fastrand()%4 == 0 { // Randomly drop x on floor. return } race.ReleaseMerge(poolRaceAddr(x)) race.Disable() } l := p.pin() if l.private == nil { l.private = x x = nil } runtime_procUnpin() if x != nil { l.Lock() l.shared = append(l.shared, x) l.Unlock() } if race.Enabled { race.Enable() } }
func init() {
runtime_registerPoolCleanup(poolCleanup)
}
4. 基准测试 下面我们来测试一下使用Pool对程序性能的影响:
package mainimport (
"sync"
"testing"
)type Student struct {
Age int
}func BenchmarkWithPool(b *testing.B) {
var s *Student
// 调用sync.Pool,初始化Student
var pool = sync.Pool{
New: func() interface{} { return new(Student) },
}
// 基准测试
for i := 0;
i < b.N;
i++ {
for j := 0;
j < 10000;
j++ {
// 从内存池中获取Student
s = pool.Get().(*Student)
s.Age = j
// 使用完将s归还到Pool
pool.Put(s)
}
}
}// 这个例子我们不使用sync.Pool
func BenchmarkWithNoPool(b *testing.B) {
var s *Student
for i := 0;
i < b.N;
i++ {
for j := 0;
j < 10000;
j++ {
s = &Student{Age: i}
s.Age++
}
}
}
测试一下:
$ go test -bench=. -benchmem
goos: windows
goarch: amd64
pkg: test
BenchmarkWithPool-810000182103 ns/op0 B/op0 allocs/op
BenchmarkWithNoPool-810000160606 ns/op80000 B/op10000 allocs/op
PASS
oktest3.771s
这里解释一下,测试结果中各字段的含义:
- 第一列为基准测试的函数名称。
- 第二列为基准测试的迭代总次数 b.N。
- 第三列为平均每次迭代所消耗的纳秒数。
- 第四列为平均每次迭代内存所分配的字节数。
- 第五列为平均每次迭代的内存分配次数。
关于sync标准库的使用请参考下面文章: 【#|Go语言标准库学习之sync二(通过sync.Pool大幅度提升程序运行性能)】Go语言标准库学习之sync一(go语言中的锁)
推荐阅读
- 数据结构和算法|LeetCode 的正确使用方式
- #|7.分布式事务管理
- #|算法设计与分析(Java实现)——贪心算法(集合覆盖案例)
- #|算法设计与分析(Java实现)—— 动态规划 (0-1 背包问题)
- #|阿尔法点亮LED灯(一)汇编语言
- #|Multimedia
- #|ARM裸机开发(汇编LED灯实验(I.MX6UL芯片))
- 基础课|使用深度优先搜索(DFS)、广度优先搜索(BFS)、A* 搜索算法求解 (n^2 -1) 数码难题,耗时与内存占用(时空复杂度)对比(附((n^2 - 1) 数码问题控
- #|学习笔记 | Ch05 Pandas数据清洗 —— 缺失值、重复值、异常值
- win10|搏一搏 单车变摩托,是时候捣鼓一下家中的小米电视机啦。