Go源码阅读:sync.Pool-创新互联

sync.Pool管理一组可以单独保存和检索的临时对象。目的是缓存已分配但未使用的对象,以供以后重用,从而减轻GC的压力。核心就是PutGetNew

成都创新互联公司专业为企业提供西充网站建设、西充做网站、西充网站设计、西充网站制作等企业网站建设、网页设计与制作、西充企业网站模板建站服务,十年西充做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。sync.Pool的结构体

使用sync.Pool需要提供一个New方法,以便在池中没有缓存的对象时,调用New创建

type Pool struct {noCopy noCopy // 静态检查机制:内置noCopy结构体的对象在第一次使用后不会再发生复制

	local     unsafe.Pointer // local 固定大小 per-P 池, 实际类型为[P]poolLocal
	localSize uintptr        // local array 的大小

	victim     unsafe.Pointer // 在上一个GC周期local被poolCleanup函数放置于此,它可能尚未被清理。     后面再讲
	victimSize uintptr        // victims array 的大小

	// 在Get方法失败的情况下,选择性的创建一个值
	New func() interface{}
}
poolLocal结构体
type poolLocalInternal struct {private interface{} // 只能被各自的P使用
	shared  poolChain   // 可以被任意P使用
}

type poolLocal struct {poolLocalInternal

    // 对齐到机器的缓存行大小,以避免false sharing  [1]
	pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
  • private只保存一个对象,且只能被拥有当前poolLocalP访问
  • shared是一个链表,可以被其他P访问
单个sync.Pool在运行时的内存状态image-20221201122101843Get

有了上面这张图,Get如何从池中获取对象,也能猜个七七八八了。

  1. 优先选择当前P对应的poolLocal.private
  2. 若取不到,优先从当前Psharehead链表的头部取出一个对象。
  3. 若取不到,则尝试从其他线程中sharetail中steal一个对象。
  4. 若取不到,则调用New创建一个对象。
func (p *Pool) Get() interface{} {// 如果启用的 race 检查则先停用
	if race.Enabled {race.Disable()
	}
	// 返回pid,和 poolLocal
	l, pid := p.pin()
	// 尝试从private中获取数据
	x := l.private
	// 获取之后将private置nil,相当于从poolLocal中移除对象
	l.private = nil
	// 若从private中获取失败
	if x == nil {// 为了更好的利用时间局部性,从 shared 头部读取对象
		x, _ = l.shared.popHead()
		// 如果读取不到,则steal获取新的缓存对象
		if x == nil {	x = p.getSlow(pid)
		}
	}
	runtime_procUnpin()
	// 恢复 race 检查
	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. 尝试从其他p的shared窃取。
  2. 若取不到,尝试从上一轮GC时被放置(惰性回收)的poolLocalprivate取对象。
  3. 若取不到,尝试从上一轮GC时被放置(惰性回收)的poolLocalshared取对象。
  4. 还是取不到,返回nil,有Get函数调用New创建一个对象。
func (p *Pool) getSlow(pid int) interface{} {// 遍历所有p的 poolLocal 尝试从shared中窃取一个对象
	size := runtime_LoadAcquintptr(&p.localSize) // load-acquire
	locals := p.local                            // load-consume
	// Try to steal one element from other procs.
	for i := 0; i< int(size); i++ {l := indexLocal(locals, (pid+i+1)%int(size))
		if x, _ := l.shared.popTail(); x != nil {	return x
		}
	}
	
	// 遍历所有p的victim
	size = atomic.LoadUintptr(&p.victimSize)
	if uintptr(pid) >= size {return nil
	}
	// 遍历当前p的victim的private
	locals = p.victim
	l := indexLocal(locals, pid)
	if x := l.private; x != nil {l.private = nil
		return x
	}
	// 遍历其他p的victim的shared
	for i := 0; i< int(size); i++ {l := indexLocal(locals, (pid+i)%int(size))
		if x, _ := l.shared.popTail(); x != nil {	return x
		}
	}
	atomic.StoreUintptr(&p.victimSize, 0)

	return nil
}
Put

将一个(不确定对象状态)的对象放入到池中,遵循以下策略。

  1. 优先放入private
  2. 如果private已经有值,则尝试放入shared
func (p *Pool) Put(x interface{}) {if x == nil {  return
   }
   // 停用 race
   if race.Enabled {  if fastrand()%4 == 0 { // Randomly drop x on floor.
         return
      }
      race.ReleaseMerge(poolRaceAddr(x))
      race.Disable()
   }
   // 获取 localPool
   l, _ := p.pin()
   // 优先放入 private
   if l.private == nil {  l.private = x
      x = nil
   }
   // 如果不能放入 private 则放入 shared
   if x != nil {  l.shared.pushHead(x)
   }
   runtime_procUnpin()

   // 恢复race
   if race.Enabled {  race.Enable()
   }
}
惰性回收

sync.Pool 的垃圾回收发生在运行时 GC 开始之前。

var poolcleanup func()
// 利用编译器标志将 sync 包中的清理注册到运行时
//go:linkname sync_runtime_registerPoolCleanup sync.runtime_registerPoolCleanup
func sync_runtime_registerPoolCleanup(f func()) {poolcleanup = f
}

// 实现缓存清理
func clearpools() {// clear sync.Pools
	if poolcleanup != nil {poolcleanup()
	}
    (...)
}
清理函数

victim的使用出现在getslow函数中,当从其他Pshared中无法窃取到对象时,会尝试从上一次GC周期时放置的缓存中获取对象。

这就涉及到了惰性回收,当GC触发前poolCleanup会将运行时中所有sync.Pool对象中的poolLocal移动到其对应victim字段,victim会保存一个GC周期后被清除。

func poolCleanup() {// 清空上一GC周期的victim缓存
   for _, p := range oldPools {  p.victim = nil
      p.victimSize = 0
   }

   // 将当前运行时中所有Pool(不同的Pool对象)中的local移动到其victim中
   for _, p := range allPools {  p.victim = p.local
      p.victimSize = p.localSize
      p.local = nil
      p.localSize = 0
   }

   // 互换oldPool和allPools,并将allPools置nil
   oldPools, allPools = allPools, nil
}
备注

才疏学浅,若有疑惑之处,很可能是笔者出错了。还望不吝赐教。

参考资料

[1] false sharing

2 欧老师:Go source study: sync.Pool

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


文章名称:Go源码阅读:sync.Pool-创新互联
文章来源:http://scjbc.cn/article/dshsps.html

其他资讯