Golang 之 sync.Pool揭秘

sync.Pool 主要通过减少GC来提升性能,是Goroutine并发安全的

sync.Pool使用

初始化Pool实例,可以通过配置new方法来声明Pool元素创建的方法

bufferpool := &sync.Pool {
    New: func() interface {} {
        println("Create new instance")
        return struct{}{}
    }
}

申请和释放,申请会返回Pool中已经存在的对象,如果没有就初始化一个对象,Pool中的对象何时释放外界是不清楚的

buffer := bufferPool.Get()
bufferPool.Put(buffer)

当有频繁创建的对象时,而且不用关注对象的状态,就用Pool,可以避免大量实例的创建与释放

// 用来统计实例真正创建的次数
var numCalcsCreated int32
// 创建实例的函数
func createBuffer() interface{} {
    // 这里要注意下,非常重要的一点。这里必须使用原子加,不然有并发问题
    atomic.AddInt32(&numCalcsCreated, 1)
    buffer := make([]byte, 1024)
    return &buffer
}
func main() {
    // 创建实例
    bufferPool := &sync.Pool{
        New: createBuffer,
    }
    numWorkers := 1024 * 1024
    var wg sync.WaitGroup
    wg.Add(numWorkers)
    for i := 0; i < numWorkers; i++ {
        go func() {
            defer wg.Done()
            // 申请一个 buffer 实例
            buffer := bufferPool.Get()
            _ = buffer.(*[]byte)
            // 释放一个 buffer 实例
            defer bufferPool.Put(buffer)
        }()
    }
    wg.Wait()
    fmt.Printf("%d buffer objects were created", numCalcsCreated)
}
输出
8 buffer objects were created
or 不固定
7 buffer objects were created

将bufferPool.Get变为buffer := createBuffer(),将输出1048576 buffer objects were created在程序运行时,进程消耗内存非常大,同时在内部runtime GC回收时会很影响性能

sync.Pool线程安全吗?

sync.Pool本身是线程安全的,get和put方法都是线程安全的,但是new函数不是线程安全的,上例中用原子加就是这个原因,不用原子加,可能最后的数字会比实际的小一点,但是在当下多核机器下几乎看不到区别

为什么 sync.Pool 不适合用于像 socket 长连接或数据库连接池?

Pool 池里的元素随时可能释放掉,释放策略完全由 runtime 内部管理;

Get 获取到的元素对象可能是刚创建的,也可能是之前创建好 cache 住的。使用者无法区分;

Pool 池里面的元素个数你无法知道;

总结:

sync.Pool 本质用途是增加临时对象的重用率,减少 GC 负担,

不能对 Pool.Get 出来的对象做预判,有可能是新的(新分配的),有可能是旧的(之前人用过,然后 Put 进去的),也无法去判读啊Pool中元素个数

sync.Pool 本身的 Get, Put 调用是并发安全的,sync.New 指向的初始化函数会并发调用,

当用完一个从 Pool 取出的实例时候,一定要记得调用 Put,否则 Pool 无法复用这个实例,通常这个用 defer 完成,

posted @ 2020-07-30 19:27  LeeJuly  阅读(363)  评论(0编辑  收藏  举报