如何在多个协程之间使用同一个 Pool 达到高效的目的呢?官方的建议是尽量减少竞争。因为 sync.pool 会为每个P分配一个子池。当一个 pool 执行Get 或者 Put 操作时,会先把当前的goroutine 固定到某个P的子池上面,然后再对该子池进行操作。每个子池里面有一个私有对象和共享列表对象,私有对象是只有对应的 P 能够访问,因为一个 P 同一时间只能执行一个协程,因此对私有对象存取操作是不需要加锁的。共享池是和其他P分享的,因此操作共享池是需要加锁的。
对于私有对象来说,是协程安全的;而共享池是协程不安全的。
对于sync.Pool中的对象拿取的步骤是:
(1)固定到某个P,尝试从私有对象获取(私有对象是协程安全的)。
(2)如果私有对象非空则返回该对象,并把私有对象置空。
(3)私有对象不存在,则尝试从当前Processor的共享池获取(共享池是协程不安全,需要锁)。
(4)如果当前Processor共享池是空的,那么尝试去其他Processor的共享池获取(也需要加锁)。
(5)如果所有子池都是空的,最后使用用户指定的New()
函数产生一个新的对象返回。
对于sync.Pool中的对象的放回步骤是:
(1)固定到某个P,如果私有对象不存在则保存为私有对象。
(2)如果私有对象存在 放入当前Processor子池的共享池中(需要加锁)。
一次 Get 操作最少 0 次加锁,最大 N(N 等于 GOMAXPROCS)次加锁。一次 Put 操作最少 0 次加锁,最多 1 次加锁。
协程具体会分配到那个 P 执行是 Go语言的协程调度系统决定的。因此在MAXPROCS>1
的情况下,当多个协程使用同一个sync.Pool,各个P的子池之间缓存的对象是否平衡以及开销如何是没办法准确衡量的。但如果协程数目和缓存的对象数目远远大于GOMAXPROCS 时,从概率上来说应该是相对平衡的。