golang sync包中的pool讲解

主要结构
// 第一次使用时,不能复制
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{}
}
//具体存储结构
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方法
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方法
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
}
基准测试
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++
        }
    }
}
$ 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 压力也更小,所以效率当然就高了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值