sync.Pool

Pool

1、用于存储拿些被分配了但是没有被使用,而未来可能会被使用的值,以减小垃圾回收的压力
2、协程安全,应该用于管理协程共享的变量,不推荐用于非协程间的对象管理
3、调动New函数,将使用函数创建一个新对象返回
4、从Pool中取出对象时,如果Pool中没有对象,将执行New(),如果没有对New进行赋值,则返回nil
5、先进后出存储原则,和栈类似Pool  一个比较好的例子是fmt包,fmt包总是需要使用一些[]byte之类的对象,
        Go建立了一个临时对象池存放看这些对象,如果需要使用一个[]byte,就去Pool里拿,如果拿不到就分配一份。
        这比起不停 生成新的[]byte,用完了在等待gc回收要高效的多

 

应用场景
    1、路由映射表

 

type Pool struct {
    noCopy noCopy   // 一个空结构,用于放置 pool在第一次使用后被复制

    local     unsafe.Pointer // per-P pool, 实际类型 [P]poolLocal
    localSize uintptr        // local的代销

    victim     unsafe.Pointer // local from previous cycle
    victimSize uintptr        // size of victims array

    // New optionally specifies a function to generate
    // a value when Get would otherwise return nil.
    // It may not be changed concurrently with calls to Get.
    New func() interface{}  //在pool中没有获取到对象的话,调用该方法生成一个变量,默认返回nil
}
// 具有存储结构
type poolLocalInternal struct {
   private interface{} // 只能有自己的P使用
   shared  poolChain   // 可以被任何P使用
}

type poolLocal struct {
   poolLocalInternal

   // Prevents false sharing on widespread platforms with
   // 128 mod (cache line size) = 0 .避免缓存false,使不同的线程操作那个不同的缓存行,多喝的情况下提升效率
   pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
var (
allPoolsMu Mutex  保护shared线程安全

// allPools is the set of pools that have non-empty primary
// caches. Protected by either 1) allPoolsMu and pinning or 2)
// STW.
allPools []*Pool  池列表

// oldPools is the set of pools that may have non-empty victim
// caches. Protected by STW.
oldPools []*Pool
)

 

func (p *Pool) Put(x interface{}) {
   。。。
    l, _ := p.pin() // 获取当前P的pool
    if l.private == nil { //私有属性为空,放入
        l.private = x
        x = nil
    }
    if x != nil {
        l.shared.pushHead(x)   // 放入shared池
    }
    。。。
 
}
func (p *Pool) Get() interface{} {
    。。。
    l, pid := p.pin()  // 获取当前P的pool
    x := l.private // 先从private读取
    l.private = nil
    if x == nil { // private没有
 
        x, _ = l.shared.popHead() // 从当前P的shared末尾取一个
        if x == nil { // 还没有取到,则去其他的P的shared取
            x = p.getSlow(pid)
func (p *Pool) getSlow(pid int) interface{} {
    //...
  // 尝试从其他的P中获取一个元素
    for i := 0; i < int(size); i++ {
     // 获取其他P的的poolLocal
        l := indexLocal(locals, (pid+i+1)%int(size))
        if x, _ := l.shared.popTail(); x != nil {
            return x
        }
    }

    //...
}
getSlow(pid int)
        }
    }
    runtime_procUnpin()
    if race.Enabled {
        race.Enable()
        if x != nil {
            race.Acquire(poolRaceAddr(x))
        }
    }// 最后还没有取到,调用NEW方法生成一个
    if x == nil && p.New != nil {
        x = p.New()
    }
    return x
}

 

 

GC

func init() {
    runtime_registerPoolCleanup(poolCleanup)//GC之前运行该函数
}

func poolCleanup() {
    // 将所有池的变量解除引用,未下一次GC做准备
1、在GC时会调用此函数
他不能分配,也不应该调用任何运行时函数
防御性地将所有东西归零,原因:
  1、防止整个个池的错误保留
  2、如果GC发生时goroutine与PUT.GET中的 l.shared一起使用,他将保留zhenggePool.因此下一周去内存消耗将增加一倍  

for _, p := range oldPools {
    // 清除将所有池对象接触引用 等待GC回收 p.victim
= nil p.victimSize = 0 } // Move primary cache to victim cache. for _, p := range allPools { p.victim = p.local p.victimSize = p.localSize p.local = nil p.localSize = 0 } // The pools with non-empty primary caches now have non-empty // victim caches and no pools have primary caches. oldPools, allPools = allPools, nil }

 

 echo

在 echo 中主要用来存储 context,因为大量的 foroutines 不断申请 context 的内存,
会给 GC 带来大的压力影响性能。所以 echo 采用 sync.pool 来优化。
// New creates an instance of Echo.
func New() (e *Echo) {
    ...
    e.pool.New = func() interface{} {
        return e.NewContext(nil, nil)
    }
    e.router = NewRouter(e)
    return
}

// NewContext returns a Context instance.
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
    return &context{
        request:  r,
        response: NewResponse(w, e),
        store:    make(Map),
        echo:     e,
        pvalues:  make([]string, *e.maxParam),
        handler:  NotFoundHandler,
    }
}

// AcquireContext returns an empty `Context` instance from the pool.
// You must return the context by calling `ReleaseContext()`.
func (e *Echo) AcquireContext() Context {
    return e.pool.Get().(Context)
}

// ReleaseContext returns the `Context` instance back to the pool.
// You must call it after `AcquireContext()`.
func (e *Echo) ReleaseContext(c Context) {
    e.pool.Put(c)
}
View Code
看完定义,我们再看看,echo 里的使用。也就是说我们通过 pool 这种形式避免了在并发大的情况下,造成的内存申请,和 GC 的压力。
// http 请求处理方法
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 从池里获取一个 context 对象
    c := e.pool.Get().(*context)
    // 重置对象
    c.Reset(r, w)
    ...
    // 用完后把 context 
    e.pool.Put(c)
}
View Code

 

 

 

 

 性能测试

package main

import (
    "sync"
    "testing"
)

const size = 10240
const count = 102400

// 开辟的内存小的话,不用使用Pool

func BenchmarkWithPool(b *testing.B) {

    var pool = sync.Pool{
        New: func() interface{} {
            a := make([]int, size)
            return &a
        },
    }
    for i := 0; i < b.N; i++ {
        for j := 0; j < count; j++ {
            s := pool.Get().(*[]int)
            pool.Put(s)
        }
    }
}

func BenchmarkWithNoPool(b *testing.B) {

    for i := 0; i < b.N; i++ {
        for j := 0; j < count; j++ {
            _ = make([]int, size)
        }
    }
}

//  -benchtime=20s -cpu=1go test -bench=. -benchmem

 

posted @ 2020-02-11 20:34  慕沁  阅读(228)  评论(0)    收藏  举报