sync.Pool

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 方法创建一个了

Guess you like

Origin blog.csdn.net/hengchi_hengchi/article/details/120478672