preface

Golang is typically used to develop and build services in high-concurrency scenarios, but since Golang’s built-in GC mechanism somewhat affects the performance of the service, to reduce frequent GC, Golang provides a mechanism for object reuse by using sync.pool to build object pools.

Sync. The Pool is introduced

First, sync.Pool is a scalable temporary object Pool that is also concurrency safe. Its scalable size is limited by the size of memory and can be thought of as a container for reusable objects. Sync.pool is designed to hold objects that have been allocated but are not used for the time being, and can be retrieved directly from the Pool when needed.

Any stored value in the pool can be deleted at any time without notification. In addition, the pool object pool can be dynamically expanded under high load, while the pool can be shrunk when it is not in use or concurrency is not high. The key idea is to reuse objects to avoid repeated creation and destruction, which can affect performance.

The name sync.Cache is a more appropriate name for sync.Pool because objects in the Pool can be recycled without notice.

Sync. Pool first declares two constructs, as follows:

// Local per-P Pool appendix.
type poolLocalInternal struct {
  private interface{} // Can be used only by the respective P.
  shared  poolChain   // Local P can pushHead/popHead; any P can popTail.
}

type poolLocal struct {
  poolLocalInternal

  // Prevents false sharing on widespread platforms with
  // 128 mod (cache line size) = 0 .
  pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
Copy the code

In order to efficiently use concurrency across multiple Goroutines, sync.pool allocates a local Pool for each P(CPU, somewhat like the GMP model), and when a Get or Put operation is performed, goroutine is associated with a Pool of P objects and then operated on that Pool.

The object pool for each P is divided into private objects, which can only be accessed by a particular P, and shared list objects, which can be accessed by any P. Since only one goroutine can be executed on a P at a time, locking is not required. However, when operating on a shared list object, multiple Goroutines may operate simultaneously, i.e. concurrently, so locking is required.

Note that the poolLocal structure has a PAD member to prevent false sharing. A common problem with cache usage is false sharing. False sharing can occur when different threads simultaneously read and write different data on the same cache line. False sharing can cause serious system performance degradation on multi-core processors. The specific explanation will not be described here.

Sync. Pool Put and Get methods

Sync. Pool has two public methods, Get and Put.

Put method

Let’s look at the source of the Put method:

// 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()
  if l.private == nil {
    l.private = x
    x = nil
  }
  ifx ! =nil {
    l.shared.pushHead(x)
  }
  runtime_procUnpin()
  if race.Enabled {
    race.Enable()
  }
}
Copy the code

Read the source code of the Put method above to know:

  • If the value Put puts in is empty, return is returned, and the following logic is not executed.
  • If not null, continue to check whether the private of the current Goroutine sets the object pool private value, and if not, assign x to that private member and set x to nil;
  • If the current goroutine’s private private value has already been assigned, append that value to the shared list.

The Get method

Get ();

func (p *Pool) Get(a) interface{} {
  if race.Enabled {
    race.Disable()
  }
  l, pid := p.pin()
  x := l.private
  l.private = nil
  if x == nil {
    // Try to pop the head of the local shard. We prefer
    // the head over the tail for temporal locality of
    // reuse.
    x, _ = l.shared.popHead()
    if x == nil {
      x = p.getSlow(pid)
    }
  }
  runtime_procUnpin()
  if race.Enabled {
    race.Enable()
    ifx ! =nil {
      race.Acquire(poolRaceAddr(x))
    }
  }
  if x == nil&& p.New ! =nil {
    x = p.New()
  }
  return x
}
Copy the code

Read the above Get method source code, you can know:

  • The first attempt is to get an object value from the object pool corresponding to local P and delete the value from the object pool.
  • If it fails to get from the local object pool, the value is obtained from the share list and removed from the share list.
  • If it fails to get from the shared list, it “steals” one from another P’s object pool and removes the value from the shared pool (p.getslow () on line 14 of the source code).
  • If that still fails, allocate a return value directly through New(), noting that the allocated value is not put into the object pool. New() returns the value of the New function registered by the user, or nil by default if the user does not register New.

The init function

Finally, let’s look at the init function, as follows:

func init(a) {
  funtime_registerPoolCleanup(poolCleanup)
}
Copy the code

You can see that a PoolCleanup function is registered during init, which removes all cached objects from sync.Pool. This registration function is run during each GC, so the sync.Pool value is only valid between GC and GC.

Sync. Pool Example

Sample code:

package main
import (
	"fmt"
	"sync"
)
// Define a Person structure with Name and Age variables
type Person struct {
	Name string
	Age int
}
// To initialize sync.Pool, the new function creates the Person structure
func initPool(a) *sync.Pool {
	return &sync.Pool{
		New: func(a) interface{} {
			fmt.Println("Create a Person.")
			return &Person{}
		},
	}
}
// main function, entry function
func main(a) {
	pool := initPool()
	person := pool.Get().(*Person)
	fmt.Println("Get person from sync.Pool for the first time:", person)
	person.Name = "Jack"
	person.Age = 23
	pool.Put(person)
	fmt.Println("Set object Name:", person.Name)
	fmt.Println("Set object Age:", person.Age)
	fmt.Println("Pool has an object. Call Get to Get it:", pool.Get().(*Person))
	fmt.Println("There are no objects in Pool, call Get again:", pool.Get().(*Person))
}
Copy the code

The running result is as follows:

Create a person. Get the person: &{from sync.Pool for the first time0} set object Name: Jack set object Age:23There is an object in the Pool. Call Get to Get: &{Jack23} create a person. Pool with no objects in it, call the Get method again: &{0}
Copy the code

conclusion

Through the above source code and examples, we can know:

  • The Get method does not guarantee the value of the object retrieved, because values put into the local object pool can be deleted at any time without notice.
  • In the value could be put in the Shared pool other goroutine to take away, so the suitable object pooling is used to store temporary cut state irrelevant data, but is not suitable for example used to store the database connection, because in the value of the object pool may be removed when the garbage collection, this violated the original intention of database connection pool to build.

Therefore, Golang’s object pool is strictly a temporary object pool, suitable for storing temporary objects that will be shared between Goroutines. The main effect is to reduce GC and improve performance. The most common use in Golang is the output buffer in the FMT package.

Github archive address: sync.pool Example code to use

Recommended reading: Red-black tree details and implementation