1、golang高并发的场景下由于内置的GC会影响性能,为了减少GC,
golang提供的对象的重用机制,即 sync.Pool 对象池。
特点:在高负载下可以动态的扩容,在不活跃时对象池会收缩
2、源码:
Pool:
// 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.
}
type poolLocal struct {
poolLocalInternal
// Prevents false sharing on widespread platforms with
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
Pool为每个P(对应CPU)都分配一个池, 每个P的子池分为私有对象和共享列表对象,私有对象只能被特定的P访问,共享列表对象可以被任何P访问。因为同一时刻一个P只能执行一个goroutine,所以无需加锁,但是对共享列表对象进行操作时,因为可能有多个goroutine同时操作,所以需要加锁。pad暂时不清楚作用。
init():
func init() {
runtime_registerPoolCleanup(poolCleanup)
}
清除Pool中的所以的缓存对象
这个注册函数会在每次GC的时候运行,所以sync.Pool中的值只在两次GC中间的时段有效
Put():
// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
// Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l := p.pin()
//当前goroutine对象池是否有私有值,如果没有则将对象赋值给私有值private,并将对象置空。
if l.private == nil {
l.private = x
x = nil
}
runtime_procUnpin()
//如果当前goroutine对象池有值,则将其加到共享列表。加锁
if x != nil {
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
if race.Enabled {
race.Enable()
}
}
Get():
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
//从本地P对应的那个本地池(只能自己访问)中获取一个对象值, 并从本地池冲删除该值。
l := p.pin()
x := l.private
l.private = nil
runtime_procUnpin()
//获取失败,那么从共享池中获取, 并从共享队列中删除该值
if x == nil {
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
//从其他P的共享池中偷一个过来,并删除共享池中的该值
if x == nil {
x = p.getSlow()
}
}
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
// 再没有就New,New出来的不会放入池子而是直接作为返回值
if x == nil && p.New != nil {
x = p.New()
}
return x
}
案例1:
var pool = sync.Pool{
New: func() interface{} {
return "1"
},
}
func TestPool_simple(test *testing.T) {
t := pool.Get().(string)
fmt.Println(t)
pool.Put("2")
t = pool.Get().(string)
fmt.Println(t)
pool.Put("3")
t = pool.Get().(string)
fmt.Println(t)
}
输出:
=== RUN TestPool_simple
1
2
3
--- PASS: TestPool_simple (0.00s)
PASS
案例2:
func TestPool_diff(test *testing.T) {
t := pool.Get().(string)
fmt.Println(t)
pool.Put("2")
pool.Put("2")
runtime.GC()
time.Sleep(1 * time.Second)
t2 := pool.Get().(string)
fmt.Println(t2)
runtime.GC()
time.Sleep(1 * time.Second)
t2 = pool.Get().(string)
fmt.Println(t2)
}
理想输出结果: 1,1,1
实际输出结果: 1,2,1
sync.Pool在gc的时候,有个poolCleanup函数:数据会转入victim里面即幸存一次GC,所有想有理想效果要两次GC。
get流程:加一步:
1、如果 private 不是空的,那就直接拿来用
2、如果 private 是空的,那就先去本地的shared队列里面从头 pop 一个
3、如果本地的 shared 也没有了,那 getSlow 去拿,其实就是去别的P的 shared 里面偷,
4、如果偷不到回去 victim 幸存者里面找
5、如果最后都没有,那就只能调用 New 方法创建一个了