Sync. Pool is a memory Pool, but I don’t know how to use it. Sync. Pool is a memory Pool. Because sync.Pool source code involves go scheduling knowledge, if not clear go scheduling, do not understand is normal. Therefore, it is suggested to learn about go scheduling first

What is the

Pool if look from the name, is the meaning of the pool, but the pool is used to describe is not appropriate, should be called a cache, because no notice by the pool is filled with objects can be recycled, so the pool is not suitable for when a socket connection pool

What’s the use of

First look at the source code comments

// Pool's purpose is to cache allocated but unused items for later reuse,
// relieving pressure on the garbage collector. That is, it makes it easy to
// build efficient, thread-safe free lists. However, it is not suitable for all
// free lists.
Copy the code

Sync. Pool is a good solution for many places that need to re-allocate and reclaim memory, because frequently allocating and reclaiming memory can put a certain amount of pressure on the GC. Sync. Pool can cache objects that are not used for the time being and can be used for the next time without having to re-allocate memory. Reuse object memory, reduce GC pressure, improve system performance.

How to use

Let’s start with an example

package main

import(
    "fmt"
    "sync"
)

func main(a) {
    // Create a pool that caches int objects
    p := &sync.Pool{
        New: func(a) interface{} {
            return 0
        },
    }

    a := p.Get().(int)
    p.Put(1)
    b := p.Get().(int)
    fmt.Println(a, b)
}
Copy the code

The output is 0, 1, you can specify a New function when you create it, and when you get an object how can you not find the cached object in the pool it will create a return using the specified New function, or return nil if there is no New function, it’s very simple to use.

After reading the source code comments, there are a few points to note:

  1. There is no relationship between the data that gets () and the data that gets ()

  2. Pool is thread-safe

  3. The size of the cache pool is unlimited (limited to memory)

  4. The poolCleanup function is registered when the pool package is initialized, which clears all cached objects in all pools. This function is called before each GC, so the sync. pool cache only lasts between gc and GC

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

The overhead of caching objects

How do you efficiently use the same pool across multiple Goroutines? The official approach is to minimize contention because sync.pool assigns a subpool to each P. As shown below.

When put and GET are performed, the Goroutine is pinned to a subpool of P and then operated on. Private objects are only used by the current P and do not need to be locked. Shared list objects are shared with other P’s, so they need to be locked

Get process:

  1. Be fixed atPIf the private object is not empty, return the value obtained, and then empty the private object
  2. If the private object is empty, get it from the shared object list (lock required)
  3. If the list of shared objects is empty, go elsewherePList of shared objectsstealOne (need to lock)
  4. If the otherPThe list of shared objects is also empty, thennewA out

You can see that a get operation is locked at least 0 times and a maximum of N (N equals MAXPROCS) times.

The process of the put

  1. Fixed to some P, if the private object is empty, put it on the private object;

  2. Otherwise, it is added to the shared list of the P subpool (lock required).

You can see that a PUT operation is locked at least 0 times and a maximum of 1 times.

In general, pools reduce the burden of the GC, but they are also expensive