7 minutes to read Go's temporary Object Pool pool and its application scenarios
- 2020-06-23 00:37:51
- OfStack
What is temporary Object pool pool?
sync.Pool gives a big comment to explain what pool is, so let's see what it says.
A temporary object pool is a collection of temporary objects that can be stored and fetched separately.
Objects in the pool are removed (released or retrieved for use) without any notice. If pool holds only 1 reference to an object, that object will most likely be reclaimed.
Pool is safe in multiple goroutine environments.
Pool is used to cache the currently unused memory that has been requested and may be used in the future to relieve GC pressure. It makes it easy and efficient to build the thread-safe free list (a data structure for dynamic memory requests). However, it is not suitable for free list for all scenarios.
Silently sharing a set of temporary elements between multiple independent threads running independently with 1 pool is a fair use scenario for pool. Pool provides a mechanism for sharing temporary elements between multiple independent client.
There is an example of using Pool in the fmt package, which maintains a dynamically sized output of buffer.
In addition, some short life cycle objects are not suitable for maintenance with pool, in which case it is not cost-effective to use pool. This is where they should use their own free list (probably referring to the go memory model for caching) < 32k small object free list) is more efficient.
Pool 1 cannot be copied once used.
Pool structure is defined as:
type Pool struct {
noCopy noCopy
local unsafe.Pointer // local P Cache pool pointer
localSize uintptr // local P Cache pool size
// When there are no possible objects in the pool
// Will be called New Functional construction 1 An object
New func() interface{}
}
There are two public methods defined in Pool, Put - adding elements to the pool; Get - Gets the element from the pool, calls New to generate the element if not, and returns nil if New is not set.
Get
Pool maintains one local pool for each P, and the local pool for P is divided into private and Shared pools private. Elements in the private pool can only be used locally by P. Elements in the Shared pool can be stolen by other P, so locking is not required when using the private pool private as compared to locking when using the Shared pool shared.
Get will first look for local private, then local shared, and finally shared for other P. If none of the above elements are available, it will finally call the New function to get the new element.
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
// Access to the local P the poolLocal object
l := p.pin()
// First get private Objects in the pool (only 1 A)
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil {
// Look for local shared Pool,
// local shared It could be something else P access
// Need to lock
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
// Look for other P the shared pool
if x == nil {
x = p.getSlow()
}
}
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
// No available element found, call New generate
if x == nil && p.New != nil {
x = p.New()
}
return x
}
getSlow to obtain available elements from shared pools in other P:
func (p *Pool) getSlow() (x interface{}) {
// See the comment in pin regarding ordering of the loads.
size := atomic.LoadUintptr(&p.localSize) // load-acquire
local := p.local // load-consume
// Try to steal one element from other procs.
pid := runtime_procPin()
runtime_procUnpin()
for i := 0; i < int(size); i++ {
l := indexLocal(local, (pid+i+1)%int(size))
// The corresponding pool Need to lock
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
l.Unlock()
break
}
l.Unlock()
}
return x
}
Put
Put prioritizes elements in the private pool; If private is not empty, it is placed in the shared pool. Interestingly, a quarter of the element may be thrown away before it is added to the pool.
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
// Throw the elements away at random ...
// Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l := p.pin()
if l.private == nil {
l.private = x
x = nil
}
runtime_procUnpin()
if x != nil {
// Shared pool access, need to be locked
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
if race.Enabled {
race.Enable()
}
}
poolCleanup
poolCleanup is called when the world pauses and garbage collection is about to begin. Memory cannot be allocated within this function and no runtime functions can be called. The reason:
Prevent the entire Pool from being left in error
If an goroutine is accessing ES117en.shared when GC occurs, the entire Pool will be retained and the next time it executes, it will have double the memory
func poolCleanup() {
for i, p := range allPools {
allPools[i] = nil
for i := 0; i < int(p.localSize); i++ {
l := indexLocal(p.local, i)
l.private = nil
for j := range l.shared {
l.shared[j] = nil
}
l.shared = nil
}
p.local = nil
p.localSize = 0
}
allPools = []*Pool{}
}
Case 1: Context pool in gin
In web application, the background creates a context Context for the current request when processing each request of the user, which is used to store the request information and corresponding information. Context has a long life cycle and user requests are in a concurrent environment, so the thread-safe Pool is ideal for maintaining temporary pools of Context objects.
Gin defines 1 pool in structure Engine:
type Engine struct {
// ... Other fields are omitted
pool sync.Pool
}
The New function of pool was defined when initializing engine:
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
// allocateContext
func (engine *Engine) allocateContext() *Context {
// Construct a new context object
return &Context{engine: engine}
}
ServeHttp:
// from pool , and convert to *Context
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset() // reset
engine.handleHTTPRequest(c)
// Throw back again pool In the
engine.pool.Put(c)
Case 2: printer pool in fmt
printer also fits the long life cycle and is likely to be used in multiple goroutine, so pool is also suitable for maintenance.
printer with its temporary object pool
// pp Used to maintain printer The state of the
// It does this by sync.Pool To reuse, to avoid asking for memory
type pp struct {
//... Field omitted
}
var ppFree = sync.Pool{
New: func() interface{} { return new(pp) },
}
Capture and Release:
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.fmt.init(&p.buf)
return p
}
func (p *pp) free() {
p.buf = p.buf[:0]
p.arg = nil
p.value = reflect.Value{}
ppFree.Put(p)
}
conclusion