Golang 之 sync.Pool

We usually use golang to build applications under high concurrency scenarios, but because golang's built-in GC mechanism will affect the performance of the application, in order to reduce GC, golang provides a mechanism for object reuse, that is, sync.Pool object pool. sync.Pool is scalable and concurrently safe. Its size is limited only by the size of memory, and can be seen as a container for the values ​​of reusable objects. The purpose of the design is to store objects that have been allocated but are not used temporarily, and are taken directly from the pool when they are needed.

The values ​​in any storage area can be deleted at any time without notice, and can be dynamically expanded under high load, and the object pool will shrink when inactive.

sync.Pool first declares two structures

// Local per-P Pool appendix.
type poolLocalInternal struct {
	private interface{}   // Can be used only by the respective P.
	shared  []interface{} // Can be used by any P.
	Mutex                 // Protects shared.
}

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 make efficient use of goroutine in multiple goroutines, sync.Pool allocates a local pool for each P (corresponding to CPU). When performing Get or Put operations, the goroutine will first be associated with a P subpool , And then operate the subpool. The sub-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 a time, there is no need to lock, but when operating on a shared list object, because there may be multiple goroutines operating at the same time, it needs to be locked.

It is worth noting that there is a pad member in the poolLocal structure, the purpose is to prevent false sharing. A common problem in the use of cache is false sharing. When different threads simultaneously read and write different data on the same cache line, false sharing may occur. False sharing can cause severe system performance degradation on multi-core processors. For details, please refer to False Sharing .

There are two public methods for type sync.Pool, one is Get and the other is Put. Let's take a look at the source code of Put first.

  1. If the value is empty, return directly.
  2. Check whether the current goroutine sets the private value of the object pool, if not, assign x to its private member, and set x to nil.
  3. If the current goroutine private value has been set, then the value is appended to the shared list.

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

  1. Try to get an object value from the local pool corresponding to the local P, and delete the value from the local pool.
  2. If the acquisition fails, get it from the shared pool and delete the value from the shared queue.
  3. If the acquisition fails, steal one from the shared pool of other P and delete the value in the shared pool (p.getSlow ()).
  4. If it still fails, assign a return value directly through New (). Note that this assigned value will not be put into the pool. New () returns the value of the New function registered by the user. If the user has not registered New, then returns nil.

func (p *Pool) Get() interface{} {
	if race.Enabled {
		race.Disable()
	}
	l := p.pin()
	x := l.private
	l.private = nil
	runtime_procUnpin()
	if x == nil {
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
		}
		l.Unlock()
		if x == nil {
			x = p.getSlow()
		}
	}
	if race.Enabled {
		race.Enable()
		if x != nil {
			race.Acquire(poolRaceAddr(x))
		}
	}
	if x == nil && p.New != nil {
		x = p.New()
	}
	return x
}

init function

Registered a PoolCleanup function during init, he will clear all cached objects in sync.Pool, this registration function will run every time GC, so the value in sync.Pool is only in the middle of two GC The period of time is valid.

func init() {
    runtime_registerPoolCleanup(poolCleanup)
}

Examples:

package main

import (
    "sync"
    "time"
    "fmt"
)

var bytePool = sync.Pool{
    New: func() interface{} {
        b := make([]byte, 1024)
        return &b
    },
}


func main()  {
    //defer
    //debug.SetGCPercent(debug.SetGCPercent(-1))
    a := time.Now().Unix()
    for i:=0;i<1000000000;i++{
        obj := make([]byte, 1024)
        _ = obj
    }
    b := time.Now().Unix()

    for j:=0;j<1000000000;j++  {
        obj := bytePool.Get().(*[]byte)
        _ = obj
        bytePool.Put(obj)
    }

    c := time.Now().Unix()
    fmt.Println("without pool ", b - a, "s")
    fmt.Println("with    pool ", c - b, "s")
}

 

It can be seen that GC has little effect on performance, because the shared list is too long and takes time. 

to sum up:

Through the above interpretation, we can see, Get method and will not get to do any of the object's value guarantee , because the values are placed in the local pool may be removed at any time, but does not notify the caller. The value put in the shared pool may be stolen by other goroutine. Therefore, the object pool is more suitable for storing some temporary state-agnostic data, but it is not suitable for storing database connection instances, because the value stored in the object pool may be deleted during garbage collection, which violates the database. The original intention of connection pool establishment.

According to the above statement, Golang's object pool is strictly a temporary object pool, suitable for storing some temporary objects that will be shared between goroutine. 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.

If you want to achieve the effect of connection pooling in Golang, you can use container / list to achieve it. There are also some ready-made implementations in the open source world, such as go-commons-pool , specific readers can go to understand.

 

 

 

Published 127 original articles · Likes 24 · Visits 130,000+

Guess you like

Origin blog.csdn.net/Linzhongyilisha/article/details/105472739