开始:
- 主要结构
- 主体流程
- PUT方法
- GET方法
- 基准测试
主要结构
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 第一次使用时,不能复制 type Pool struct { noCopy noCopy //空结构,用来防止pool在第一次使用时被复制。 local unsafe.Pointer // 本地固定大小的pool池,其实类型为[P]poolLocal localSize uintptr // 本地固定pool池的大小 victim unsafe.Pointer // 表示上一个周期的local victimSize uintptr // 同上 // New 在 pool 中没有获取到,调用该方法生成一个变量 New func() interface{} } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | //具体存储结构 type poolLocalInternal struct { private interface{} // 私有的,只能由自己的 P 使用 shared poolChain // 共享的,可以被任何的 P 使用 } type poolLocal struct { poolLocalInternal // Prevents false sharing on widespread platforms with // 128 mod (cache line size) = 0 . // 避免缓存 false sharing,使不同的线程操纵不同的缓存行,多核的情况下提升效率。 pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte } |
主体流程
看完整个结构后,我们先了解一下整个流程。
PUT方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | func (p *Pool) Put(x interface{}) { ... //获取当前p的pool(池) l, _ := p.pin() if l.private == nil { // 如果私有属性为nil,那么将x写入到private l.private = x x = nil } if x != nil { //如果x没有复制给私有属性,则将其推到共享属性中 l.shared.pushHead(x) } runtime_procUnpin() if race.Enabled { race.Enable() } } |
GET方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | func (p *Pool) Get() interface{} { if race.Enabled { race.Disable() } // 获取当前p的pool(池) l, pid := p.pin() x := l.private l.private = nil if x == nil { // 从当前 P 的 shared 末尾取一个 x, _ = l.shared.popHead() if x == nil { // 还没有取到 则去其他 P 的 shared 取 x = p.getSlow(pid) } } runtime_procUnpin() if race.Enabled { race.Enable() if x != nil { race.Acquire(poolRaceAddr(x)) } } // 最后还没取到 调用 NEW 方法生成一个 if x == nil && p.New != nil { x = p.New() } return x } |
基准测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package main import ( "sync" "testing" ) type S struct { num int } func BenchmarkWithPool(b *testing.B) { var s *S var pool = sync.Pool{ New: func() interface{} { return new(S) }, } for i := 0; i < b.N; i++ { for j := 0; j < 10000; j++ { s = pool.Get().(*S) s.num = 1 s.num++ pool.Put(s) } } } func BenchmarkWithNoPool(b *testing.B) { var s *S for i := 0; i < b.N; i++ { for j := 0; j < 10000; j++ { s = &S{num: 1} s.num++ } } } |
1 2 3 4 5 6 | $ go test -bench=. -benchmem goos: darwin goarch: amd64 BenchmarkWithPool-4 10000 253269 ns/op 0 B/op 0 allocs/op BenchmarkWithNoPool-4 10000 175742 ns/op 80000 B/op 10000 allocs/op |
可以看到每次分配的内存 0 B vs 80000 B,每次内存分配次数 0 vs 10000。因为每次测试,我们执行了10000次迭代,所以看到没使用池的内存单次分配是 8B(即 结构 S 占的内存),单次分配次数为 1次。但是在每次执行的时间上使用池比不使用池是要多的,比较使用池涉及到池的维护,也算是正常的。这样看来,在高并发的场景下,context 的复用率非常高,所带来的 GC 压力也更小,所以效率当然就高了。