Detailed explanation of the use of sync.pool in golang

Time:2021-9-5

preface

We usually use golang to develop and build services in high concurrency scenarios. However, because the built-in GC mechanism of golang will affect the performance of services, golang provides an object reuse mechanism to reduce frequent GC, that is, to build an object pool using sync.pool.

Sync.pool introduction

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

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

Personally, I think its name is misleading because the objects contained in the pool can be recycled without notice. I think the name of sync.cache is more suitable for the name of sync.pool.

Sync.pool first declares two structures, 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
}

In order to efficiently use concurrency in multiple goroutines, sync.pool will allocate a local pool for each P (corresponding CPU, which is a bit like GMP model here). When performing get or put operations, goroutine will be associated with the object pool of a p first, and then the pool will be operated.

The object pool of each P is divided into private objects and shared list objects. Private objects can only be accessed by a specific P, and shared list objects can be accessed by any P. Because a P can only execute one goroutine at the same time, it does not need to be locked. However, when operating on shared list objects, it needs to be locked because multiple goroutines may operate at the same time, that is, concurrent operations.

It should be noted that there is a pad member in the poollocal structure to prevent false sharing. A common problem in cache usage is false sharing. False sharing may occur when different threads read and write different data on the same cache line at the same time. False sharing can cause severe system performance degradation on multi-core processors. The specific explanation will not be repeated here.

Put and get methods of sync.pool

Sync.pool has two public methods, one is get and the other is put.

Put method

Let’s first look at the source code of put method, as follows:


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

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

  • If the value put in by put is empty, it will be returned directly and the following logic will not be executed;
  • If it is not empty, continue to check whether the private of the current goroutine sets the private value of the object pool. If not, assign x to the private member and set X to nil;
  • If the private value of the current goroutine has been assigned, the value is appended to the shared list.

Get method

Let’s look at the source code of the get method, as follows:


func (p *Pool) Get() 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()
    if x != nil {
      race.Acquire(poolRaceAddr(x))
    }
  }
  if x == nil && p.New != nil {
    x = p.New()
  }
  return x
}

After reading the source code of the above get method, you can know:

  • First, try to get an object value from the object pool corresponding to the local P, and delete the value from the object pool.
  • If getting from the local object pool fails, get from the share list and delete the value from the share list.
  • If it fails to obtain from the shared list, it will “steal” one from the object pool of other P and delete the value in the shared pool (p.getslow(), line 14 in the source code).
  • If it still fails, allocate a return value directly through new (). Note that the allocated value will not be put into the object pool. New () is the value of the new function that returns the user’s registration. If the user does not register new, nil is returned by default.

Init function

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


func init() {
  funtime_registerPoolCleanup(poolCleanup)
}

You can see that a poolcleanup function is registered during init, which will clear all cached objects in sync.pool. This registration function will run during each GC, so the value in sync.pool is only valid in the middle of two GCs.

Sync.pool usage example

Example code:

package main
import (
 "fmt"
 "sync"
)
//Define a person structure with name and age variables
type Person struct {
 Name string
 Age int
}
//Initialize sync.pool, and the new function is to create the person structure
func initPool() *sync.Pool {
 return &sync.Pool{
  New: func() interface{} {
   FMT. Println ("create a person.")
   return &Person{}
  },
 }
}
//Main function
func main() {
 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 ("there is an object in the pool, call the get method to get:", pool. Get(). (* person))
 FMT. Println ("there is no object in the pool, call the get method again:", pool. Get(). (* person))
}

The operation results are as follows:

Create a person
Get person from sync.pool for the first time: & {0}
Set object name:   Jack
Set object age:   twenty-three
There is an object in the pool. Call the get method to get: & {Jack 23}
Create a person
There is no object in the pool. Call the get method again: & {0}

summary

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

  • The get method does not guarantee the obtained object value, because the value put into the local object pool may be deleted at any time without notification.
  • The values put into the shared pool may be taken away by other goroutines, so the object pool is more suitable for storing some data irrelevant to the temporary cutting state, but it is not suitable for storing database connection instances, because the values stored in the object pool may be deleted during garbage collection, which violates the original intention of establishing the database connection pool.

It can be seen that the object pool of golang is strictly a temporary object pool, which is suitable for storing some temporary objects that will be shared among goroutines. The main function is to reduce GC and improve performance. The most common usage scenario in golang is the output buffer in the FMT package.

Code GitHub archive address:Sync.pool usage example code

This is the end of this article about the use of golang sync.pool. For more information about golang sync.pool, please search for previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!