sync Pool 代码阅读

Pool结构体

type Pool struct {
	noCopy noCopy

	local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
	localSize uintptr        // size of the local array

	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	// It may not be changed concurrently with calls to Get.
	New func() interface{}
}

poolLocal:
每个p有不一样的对象,其中private只能提供给p使用,shared可以被其他p进行steal,但是需要加锁

// Local per-P Pool appendix.
type poolLocalInternal struct {
	private interface{}   // Can be used only by the respective P.
	shared  []interface{} // Can be used by any P.
	Mutex                 // Protects shared.
}

Get方法

func (p *Pool) Get() interface{} {
	...
	l := p.pin()  // 选到p对应的poolLocal
	x := l.private
	l.private = nil
	runtime_procUnpin()
	if x == nil { // private 没有
		l.Lock() // 加锁
		last := len(l.shared) - 1
		if last >= 0 { // 尝试从自己共享的对象取一个
			x = l.shared[last]
			l.shared = l.shared[:last]
		}
		l.Unlock()
		if x == nil {  // 没办法只能去其他p去steal
			x = p.getSlow()
		}
	}
	...
	if x == nil && p.New != nil {
		x = p.New() // 创建一个
	}
	return x
}

get方法流程:

  1. p从自己私有对象获取(无锁)
  2. p从自己共享对象获取(局部锁,一个p)
  3. p从其他p偷取共享对象(循环上锁解锁,所以slow)
  4. 没有在生成

Put方法

// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	...
	l := p.pin() // 获取p的poollocal对象
	if l.private == nil { // 优先设置私有
		l.private = x
		x = nil
	}
	runtime_procUnpin()
	if x != nil {
		l.Lock()
		l.shared = append(l.shared, x) // 追加到自己共享区中
		l.Unlock()
	}
	...
}

Put方法流程:

  1. 尝试加入私有对象中
  2. 追加到共享对象中

Pin和getSlow

// pin pins the current goroutine to P, disables preemption and returns poolLocal pool for the P.
// Caller must call runtime_procUnpin() when done with the pool.
func (p *Pool) pin() *poolLocal {
	pid := runtime_procPin()
	// In pinSlow we store to localSize and then to local, here we load in opposite order.
	// Since we've disabled preemption, GC cannot happen in between.
	// Thus here we must observe local at least as large localSize.
	// We can observe a newer/larger local, it is fine (we must observe its zero-initialized-ness).
	s := atomic.LoadUintptr(&p.localSize) // load-acquire 设置保证GC无法发生
	l := p.local                          // load-consume
	if uintptr(pid) < s {  // p已经存在
		return indexLocal(l, pid)
	}
	return p.pinSlow() // p是新的,生成poollocal结构,并且注册在全局对象allPools上,GC时会扫描。
}

func (p *Pool) pinSlow() *poolLocal {
	// Retry under the mutex.
	// Can not lock the mutex while pinned.
	runtime_procUnpin()
	allPoolsMu.Lock()
	defer allPoolsMu.Unlock()
	pid := runtime_procPin()
	// poolCleanup won't be called while we are pinned.
	s := p.localSize
	l := p.local
	if uintptr(pid) < s {
		return indexLocal(l, pid)
	}
	if p.local == nil {
		allPools = append(allPools, p) // 注册
	}
	// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.
	size := runtime.GOMAXPROCS(0)
	local := make([]poolLocal, size)
	atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
	atomic.StoreUintptr(&p.localSize, uintptr(size))         // store-release
	return &local[pid]
}

getslow:

func (p *Pool) getSlow() (x interface{}) {
	// See the comment in pin regarding ordering of the loads.
	size := atomic.LoadUintptr(&p.localSize) // load-acquire
	local := p.local                         // load-consume
	// Try to steal one element from other procs.
	pid := runtime_procPin()
	runtime_procUnpin()
	for i := 0; i < int(size); i++ { // local全局pid到poollocal的对象
		l := indexLocal(local, (pid+i+1)%int(size))
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
			l.Unlock()
			break
		}
		l.Unlock()
	}
	return x
}

GC

GC会把所有poo清空 。。。注意千万别在意对象的生命周期。。

func poolCleanup() {
	// This function is called with the world stopped, at the beginning of a garbage collection.
	// It must not allocate and probably should not call any runtime functions.
	// Defensively zero out everything, 2 reasons:
	// 1. To prevent false retention of whole Pools.
	// 2. If GC happens while a goroutine works with l.shared in Put/Get,
	//    it will retain whole Pool. So next cycle memory consumption would be doubled.
	for i, p := range allPools {
		allPools[i] = nil
		for i := 0; i < int(p.localSize); i++ {
			l := indexLocal(p.local, i)
			l.private = nil
			for j := range l.shared {
				l.shared[j] = nil
			}
			l.shared = nil
		}
		p.local = nil
		p.localSize = 0
	}
	allPools = []*Pool{}
}

var (
	allPoolsMu Mutex
	allPools   []*Pool
)

func init() {
	runtime_registerPoolCleanup(poolCleanup)
}

总结

  1. pool 使用需要谨慎,优先确认是否有必要pool存储临时对象。使用前应该了解benchmark评估下与直接生成对象的性能开销对比。优先建议通过堆内存逃逸检查下,如果能把临时对象控制在栈中最好。
  2. 注意千万别在意对象的生命周期,有类似场景应该避开。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值