Hi, I’m Haohongfan.

Sync. Pool is supposed to be the star data structure in Go. There are many excellent articles about this structure. This article takes a brief look at sync.Pool. To be honest sync.Pool is not a concurrency primitive that we use very often in daily development.

Despite the low frequency of use, sync.Pool is undeniably Go’s ace in the bag, and when used properly sync.Pool can give our applications a performance boost. This article will give you a clear understanding of Sync. Pool in terms of usage, source analysis, application scenarios, and more.

use

Sync. Pool is easy to use, but it can be tricky to get it right, because you might see a bunch of wrong examples online. Be careful when searching for sync.Pool examples.

Sync. Pool is a memory Pool. Usually memory pools are used to prevent memory leaks (such as C/C++). Sync. Pool is not for this purpose. All gC-enabled languages have the problem of garbage collection STW. The more memory blocks that need to be reclaimed, the longer the STW lasts. So if we could get the variables that come out of new, that would never get recycled, that would take some of the pressure off GC.

Examples of correct use (the following demo is selected from GIN)

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}
Copy the code

It is important to note that the memory space is first obtained by Get, then processed based on the memory, and then Put back to sync.pool.

The Pool structure

Source schematic

A simple process can be summarized as follows:

Sync. The Pool

Will the contents of the Pool be cleaned? Will data be lost?

Sync. Pool Periodically cleans data in sync.Pool during each GC cycle. Periodically clearing data does not cause data loss.

There are several aspects to this problem.

  1. When poolClean sets pool.local to nil, the value is still valid, is marked black by GC, is not collected by GC, and is readded to sync.Pool when Put
  2. Values Put to sync.Pool in the first GC cycle that are not used by Get in the second GC cycle will be Put in local.victim. If it is not used after the third GC cycle, it will be collected by GC.

What is the relationship between Runtime.GOMAXPROCS and pool?

s := p.localSize
l := p.local
if uintptr(pid) < s {
    return indexLocal(l, pid), pid
}

if p.local == nil {
    allPools = append(allPools, p)
}
// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.
size := runtime.GOMAXPROCS(0)
local := make([]poolLocal, size)
atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
runtime_StoreReluintptr(&p.localSize, uintptr(size))     // store-release
Copy the code

Runtime. GOMAXPROCS(0) gets the current maximum number of p’s. The number of poolLocal in sync.Pool is affected by the number of p, and runtime.GOMAXPROCS(0) poolLocal. In some cases we will use Runtime.gomaxprocs (N) to change the number of p, which will cause sync.pool’s Pool. PoolLocal to free up new space.

Why do you want runtime.GOMAXPROCS local?

Local is a poolLocal structure. This structure is composed of a private + shared list. If there is only one local, it needs to be locked. Local per P reduces data race problems caused by locking.

What does New() do? What happens if there is no New?

Pool = pool = pool = pool = pool = pool = pool = pool = pool = pool = pool = pool = pool = pool = pool = pool = pool It’s the New() function that really opens up memory. After the memory space created by New() is used up, the pool.Put function is called to Put it into sync.pool for reuse.

What happens if the New function is not initialized? Obviously sync.Pool is disabled because there is no place to initialize memory.

What happens when you Put and then Get?

It is important to note that the following example is not used correctly

func main(a){
    pool:= sync.Pool{
        New: func(a) interface{} {
            return item{}
        },
    }
    pool.Put(item{value:1})
    data := pool.Get()
    fmt.Println(data)
}
Copy the code

If you run directly through the example, you get the result you want, but in some cases it’s not the result you want.

In the Pool.Get comment there is the following sentence: “Callers should not assume any relation between values passed to Put and the values returned by Get.”, Sync.Pool is not a map or slice, so it may not be available. The sync.Pool data structure does not support this.

As mentioned earlier, sync.Pool is easy to mislead with the wrong example, so this is the way to write it. Why does Put and Get have problems?

  • Case 1: The poolCleanup function of sync.Pool is called during system GC. Values Put to sync.Pool may be released during a GC cycle because they may not be used.
  • Case 2: The p bound by different goroutines may be different. The value of the current p goroutine in sync.Pool may be fetched by other p goroutine, resulting in the current goroutine can no longer fetch this value.
  • Case 3: Using Runtime.gomaxprocs (N) to change the number of p causes the Pool. PoolLocal of sync.Pool to free up new space, causing sync.Pool to be freed.
  • Situation 4: Many more

Does getting without putting leak memory?

If you use another Pool, such as a connection Pool, if you take a connection and don’t put it back into the Pool, the connection Pool will leak. Is this the same problem with sync.Pool?

Pool.Get: Pool.Get: Pool.Get: Pool.Get: Pool.Get: Pool. The content of the New itself is controlled by the system GC. So sync.Pool will not leak memory if we provide a New implementation that does not leak memory. When a New variable is no longer used, it is collected by the system GC.

If you do not Put back sync.Pool, you will have to call New every time you Get it to claim space from the stack, which will not relieve GC pressure.

Usage scenarios

As mentioned above, sync.Pool is not a common structure in business development. There is no need to assume that a piece of code will have a strong performance problem and start using sync.Pool. Sync. Pool is mainly used to solve the problem of excessive Go GC pressure. Therefore, sync.Pool is usually used when high concurrent online services have GC problems and need to be optimized.

Points for Attention

  1. Sync. Pool cannot be replicated either.
  2. Good usage: the value from pool.Get can be reset to prevent junk data pollution.

This article is based on Go source version 1.16.2

Refer to the link

  1. Sync. Pool www.cnblogs.com/qcrao-2018/…
  2. What are the disadvantages of sync.Pool? Mp.weixin.qq.com/s/2ZC1BWTyl…
  3. How is Sync. Pool optimized in Go 1.13? Colobu.com/2019/10/08/…

The profile of Sync. Pool is almost done here. Anyone who wants to talk to me can leave a comment in the comments section.


Sync. The Pool full flow chart for a link: link: pan.baidu.com/s/1T5e8qCzp… Password: nGEA Other module flow charts, please follow the public number HHFCodeRv reply 1 to obtain.

Learning information sharing, follow the public account reply instructions:

  • Return to 0, get Go Face Sutra
  • Reply 1, get “Go source flow chart”