sync.Pool 包

简而言之,sync.Pool 其实拿的是大段的连续内存,因为需要并发安全来存放和拿出这些内存对象,在为数不多的 P 上的竞争会比在很多个 G 上同时竞争的锁粒度要小得多,同时为了更少的竞争,pool 在每个 P 上又有自建有一段私有的内存,多段连续内存池通过每个 P 竞争锁的方式来互斥访问内存池,每个 P 自己有一个公有池供其他P进行偷取,每次拿元素的时候优先从内部拿可以无锁返回,但是需要事先锁定 P ,不允许运行时的调度器打断这一过程,因此实现起来稍显复杂

// Local per-P Pool appendix.
type poolLocalInternal struct {
	private interface{}   // 内部临时池,仅供对应 P 自己使用,无锁
	shared  []interface{} // 其他 P 可以访问的公有池(有锁保护)
	Mutex                 // 保护 P 的公有池访问的锁
}

// 通过 P 的 ID 作为映射使得不同 P 之间可以可以相互偷取其他池里的内存
var (
	allPoolsMu Mutex
	allPools   []*Pool	// 所有P可触及的所有公共池的引用
)

Put

放置内存进来的时候优先放置在私有 private 下,如果私有的放过了且还未被拿走,那么新 put 进来的内存就放置在公共内存池中

内存偷取

当前的 P 中没有多余的内存的时候,我们需要先获得其他 P 的公共内存池的互斥锁,然后从池中直接拿出来返回

垃圾回收

func poolCleanup() {
	// 在运行时 STW 的时候依然会回收所有的池中的内存
	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{}
}

// import 的时候就会向运行时自动注册这个函数
func init() {
	runtime_registerPoolCleanup(poolCleanup)
}

通过 Pin 操作禁止抢占 P 的和多个 P 之间加互斥锁的方式来实现并发安全

func runtime_procPin() int
func runtime_procUnpin()