返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

pool

发布于 2024-10-12 19:15:54 字数 4990 浏览 0 评论 0 收藏 0

并发安全对象池。通过复用,减少临时对象,降低垃圾回收压力。
池内部闲置对象(无外部引用)可被垃圾回收,无通知。

源码剖析

每个 Pool 实例管理多个 poolLocal ,与 P 对应。
动态数组 [localSize]poolLocal ,以 P.id 为索引。

// sync/pool.go

type Pool struct {
	noCopy noCopy

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

    // 每次垃圾回收,都会将 local 缓存
    // 改成 victim 受害缓存,以便下轮回收。
    // 当 local 找不到时,也会尝试受害缓存。
    
	victim     unsafe.Pointer // local from previous cycle
	victimSize uintptr        // size of victims array

	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	New func() any
}

缓存

相关操作通过 pin 返回当前 P 所属 poolLocal 缓存。
该方法会初始化 Pool.local 内存,并添加到 allpools 全局列表。

// pin pins the current goroutine to P, disables preemption and
// returns poolLocal pool for the P and the P's id.

func (p *Pool) pin() (*poolLocal, int) {
    
    // P.id
	pid := runtime_procPin()
    
	s := runtime_LoadAcquintptr(&p.localSize) // load-acquire
	l := p.local                              // load-consume
    
    // 索引号在有效范围内。
	if uintptr(pid) < s {
		return indexLocal(l, pid), pid
	}
    
	return p.pinSlow()
}

// poolLocal[pid]
func indexLocal(l unsafe.Pointer, i int) *poolLocal {
	lp := unsafe.Pointer(uintptr(l) + 
                         uintptr(i)*unsafe.Sizeof(poolLocal{}))
	return (*poolLocal)(lp)
}
func (p *Pool) pinSlow() (*poolLocal, int) {
    
	runtime_procUnpin()
    
    // !!!!
	allPoolsMu.Lock()
	defer allPoolsMu.Unlock()
    
	pid := runtime_procPin()
    
    // 再次确认是否已存在。
	s := p.localSize
	l := p.local
	if uintptr(pid) < s {
		return indexLocal(l, pid), pid
	}
    
    // 初始化 Pool.local,添加到全局列表(新建)。
    // 进程内可有多个 Pool 实例,列表供 GC 清理用。
    
	if p.local == nil {
		allPools = append(allPools, p)
	}
    
    // 分配 Pool.local 内存。
	size := runtime.GOMAXPROCS(0)     // Ps number
	local := make([]poolLocal, size)
    
	atomic.StorePointer(&p.local, unsafe.Pointer(&local[0]))
	runtime_StoreReluintptr(&p.localSize, uintptr(size))    
    
	return &local[pid], pid
}

获取

每个 P.poolLocal 内有两处缓存。

  • private : 私有。仅一个闲置对象,快速分配。
  • shared : 共享。可能被其他 P 偷窃。

优先级: private > shared > steal > new

type poolLocalInternal struct {
	private  any       
	shared   poolChain  // doubly-linked list queue
}

type poolLocal struct {
	poolLocalInternal
}
func (p *Pool) Get() any {
    
    // P.poolLocal。
	l, pid := p.pin()
    
    // 私有对象。
	x := l.private
	l.private = nil
    
	if x == nil {
        
        // 从公有获取。
		x, _ = l.shared.popHead()
        
        // 从其他 P.poolLocal 窃取。
		if x == nil {
			x = p.getSlow(pid)
		}
	}
    
	runtime_procUnpin()
    
    // 新建。
	if x == nil && p.New != nil {
		x = p.New()
	}
    
	return x
}
func (p *Pool) getSlow(pid int) any {
    
	size := runtime_LoadAcquintptr(&p.localSize)
	locals := p.local                           
    
	// 遍历所有 P.localPool.shared,偷窃。
	for i := 0; i < int(size); i++ {
        
        // 这个公式让当前 pid 在最后遍历。
		l := indexLocal(locals, (pid+i+1)%int(size))
        
		if x, _ := l.shared.popTail(); x != nil {
			return x
		}
	}

    // 尝试从 victim 受害缓存中获取 ...
        
	return nil
}

放回

所获取对象已从 Pool 移除,须显式放回。

func (p *Pool) Put(x any) {
    
    // 空对象,放弃。
	if x == nil {
		return
	}
    
    // private。
	l, _ := p.pin()
	if l.private == nil {
		l.private = x
		x = nil
	}
    
    // shared.
	if x != nil {
		l.shared.pushHead(x)
	}
    
	runtime_procUnpin()
}

清理

当 GC 启动时,调用 clearpools 清理对象池。

// runtime/mgc.go

func gcStart(trigger gcTrigger) {
    clearpools()
}

// ------------------------

var poolcleanup func()

func clearpools() {
	// clear sync.Pools
	if poolcleanup != nil {
		poolcleanup()
	}
}

对象池实现不属于运行时,需额外注入。

// runtime/mgc.go
func sync_runtime_registerPoolCleanup(f func()) {
	poolcleanup = f
}

// --------------------------

// sync/pool.go
func init() {
	runtime_registerPoolCleanup(poolCleanup)
}

被清理的都是 Pool 内闲置对象,那些被 Get 取走的不在此列。

// sync/pool.go

var (
	allPools []*Pool
	oldPools []*Pool
)

func poolCleanup() {
    
	// This function is called with the world stopped, 
    // at the beginning of a garbage collection.

	// 放弃上轮受害缓存引用,使其可回收。
	for _, p := range oldPools {
		p.victim = nil
		p.victimSize = 0
	}

	// 将主缓存(local)转成受害缓存(victim)。
	for _, p := range allPools {
		p.victim = p.local
		p.victimSize = p.localSize
		p.local = nil
		p.localSize = 0
	}

    // 全局列表成老旧列表,新 allPools 由 pin 创建。
	oldPools, allPools = allPools, nil
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文