[Go] sync.Pool 的实现原理 和 适用场景

摘录一:

Go 1.3 的 sync 包中加入一个新特性:Pool。

官方文档可以看这里 http://golang.org/pkg/sync/#Pool

这个类设计的目的是用来保存和复用临时对象,以减少内存分配,降低CG压力。

type Pool
    func (p *Pool) Get() interface{}
    func (p *Pool) Put(x interface{})
    New func() interface{}  

Get 返回 Pool 中的任意一个对象。

如果 Pool 为空,则调用 New 返回一个新创建的对象。

如果没有设置 New,则返回 nil。

还有一个重要的特性是,放进 Pool 中的对象,会在说不准什么时候被回收掉。

所以如果事先 Put 进去 100 个对象,下次 Get 的时候发现 Pool 是空也是有可能的。

不过这个特性的一个好处就在于不用担心 Pool 会一直增长,因为 Go 已经帮你在 Pool 中做了回收机制。

这个清理过程是在每次垃圾回收之前做的。垃圾回收是固定两分钟触发一次。

而且每次清理会将 Pool 中的所有对象都清理掉!

package main

import(
    "sync"
    "log"
)

func main(){
    // 建立对象
    var pipe = &sync.Pool{New:func()interface{}{return "Hello, BeiJing"}}

    // 准备放入的字符串
    val := "Hello,World!"

    // 放入
    pipe.Put(val)

    // 取出
    log.Println(pipe.Get())

    // 再取就没有了,会自动调用NEW
    log.Println(pipe.Get())
}

// 输出
2014/09/30 15:43:30 Hello, World!
2014/09/30 15:43:30 Hello, BeiJing

摘自:http://www.nljb.net/default/sync.Pool/

摘录二:

众所周知,go 是自动垃圾回收的(garbage collector),这大大减少了程序编程负担。但 gc 是一把双刃剑,带来了编程的方便但同时也增加了运行时开销,使用不当甚至会严重影响程序的性能。因此性能要求高的场景不能任意产生太多的垃圾(有gc但又不能完全依赖它挺恶心的),如何解决呢?那就是要重用对象了,我们可以简单的使用一个 chan 把这些可重用的对象缓存起来,但如果很多 goroutine 竞争一个 chan性能肯定是问题.....由于 golang 团队认识到这个问题普遍存在,为了避免大家重造车轮,因此官方统一出了一个包 Pool。但为什么放到 sync 包里面也是有的迷惑的,先不讨论这个问题。

先来看看如何使用一个 pool:

package main  

import(
    "fmt"
    "sync"
)  

func main() {
    p := &sync.Pool{
        New: func() interface{} {
            return 0
        },
    }  

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

上面创建了一个缓存 int 对象的一个 pool,先从池获取一个对象然后放进去一个对象再取出一个对象,程序的输出是 0 1。创建的时候可以指定一个 New 函数,获取对象的时候如何在池里面找不到缓存的对象将会使用指定的 new 函数创建一个返回,如果没有 new 函数则返回 nil。用法是不是很简单,我们这里就不多说,下面来说说我们关心的问题:

1、缓存对象的数量和期限

上面我们可以看到 pool 创建的时候是不能指定大小的,所有 sync.Pool 的缓存对象数量是没有限制的(只受限于内存),因此使用 sync.pool 是没办法做到控制缓存对象数量的个数的。另外 sync.pool 缓存对象的期限是很诡异的,先看一下 src/pkg/sync/pool.go 里面的一段实现代码:

func init() {
    runtime_registerPoolCleanup(poolCleanup)
}  

可以看到 pool 包在 init 的时候注册了一个 poolCleanup 函数,它会清除所有的 pool 里面的所有缓存的对象,该函数注册进去之后会在每次 gc 之前都会调用,因此 sync.Pool 缓存的期限只是两次 gc 之间这段时间。例如我们把上面的例子改成下面这样之后,输出的结果将是 0 0。正因 gc 的时候会清掉缓存对象,也不用担心 pool 会无限增大的问题。

a := p.Get().(int)
p.Put(1)
runtime.GC()
b := p.Get().(int)
fmt.Println(a, b)  

这是很多人错误理解的地方,正因为这样,我们是不可以使用sync.Pool去实现一个socket连接池的。

2、缓存对象的开销

如何在多个 goroutine 之间使用同一个 pool 做到高效呢?官方的做法就是尽量减少竞争,因为 sync.pool 为每个 P(对应 cpu,不了解的童鞋可以去看看 golang 的调度模型介绍)都分配了一个子池,如下图:

当执行一个 pool 的 get 或者 put 操作的时候都会先把当前的 goroutine 固定到某个P的子池上面,然后再对该子池进行操作。每个子池里面有一个私有对象和共享列表对象,私有对象是只有对应的 P 能够访问,因为一个 P 同一时间只能执行一个 goroutine,因此对私有对象存取操作是不需要加锁的。共享列表是和其他 P 分享的,因此操作共享列表是需要加锁的。

获取对象过程是:

1)固定到某个 P,尝试从私有对象获取,如果私有对象非空则返回该对象,并把私有对象置空;

2)如果私有对象是空的时候,就去当前子池的共享列表获取(需要加锁);

3)如果当前子池的共享列表也是空的,那么就尝试去其他P的子池的共享列表偷取一个(需要加锁);

4)如果其他子池都是空的,最后就用用户指定的 New 函数产生一个新的对象返回。

可以看到一次 get 操作最少 0 次加锁,最大 N(N 等于 MAXPROCS)次加锁。

归还对象的过程:

1)固定到某个 P,如果私有对象为空则放到私有对象;

2)否则加入到该 P 子池的共享列表中(需要加锁)。

可以看到一次 put 操作最少 0 次加锁,最多 1 次加锁。

由于 goroutine 具体会分配到那个 P 执行是 golang 的协程调度系统决定的,因此在 MAXPROCS>1 的情况下,多 goroutine 用同一个 sync.Pool 的话,各个 P 的子池之间缓存的对象是否平衡以及开销如何是没办法准确衡量的。但如果 goroutine 数目和缓存的对象数目远远大于 MAXPROCS 的话,概率上说应该是相对平衡的。

总的来说,sync.Pool 的定位不是做类似连接池的东西,它的用途仅仅是增加对象重用的几率,减少 gc 的负担,而开销方面也不是很便宜的。

摘自:http://blog.csdn.net/yongjian_lian/article/details/42058893

时间: 2024-11-05 11:07:28

[Go] sync.Pool 的实现原理 和 适用场景的相关文章

sync.Pool 的实现原理 和 适用场景

原文链接 摘录一: Go 1.3 的 sync 包中加入一个新特性:Pool. 官方文档可以看这里 http://golang.org/pkg/sync/#Pool 这个类设计的目的是用来保存和复用临时对象,以减少内存分配,降低CG压力. 1 2 3 4 type Pool      func (p *Pool) Get() interface{}      func (p *Pool) Put(x interface{})      New func() interface{} Get 返回

深入Golang之sync.Pool详解

我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池. sync.Pool是可伸缩的,并发安全的.其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器. 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取. 任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩. sync.Pool首先

golang sync.Pool包的使用和一些注意地方

package main; import ( "sync" "fmt" "net" "runtime" ) //sync.Pool是一个可以存或取的临时对象集合 //sync.Pool可以安全被多个线程同时使用,保证线程安全 //注意.注意.注意,sync.Pool中保存的任何项都可能随时不做通知的释放掉,所以不适合用于像socket长连接或数据库连接池. //sync.Pool主要用途是增加临时对象的重用率,减少GC负担.

go语言学习--go的临时对象池--sync.Pool

一个sync.Pool对象就是一组临时对象的集合.Pool是协程安全的. Pool用于存储那些被分配了但是没有被使用,而未来可能会使用的值,以减小垃圾回收的压力.一个比较好的例子是fmt包,fmt包总是需要使用一些[]byte之类的对象,golang建立了一个临时对象池,存放着这些对象,如果需要使用一个[]byte,就去Pool里面拿,如果拿不到就分配一份. 这比起不停生成新的[]byte,用完了再等待gc回收来要高效得多. type buffer []byte // pp是用于存储printe

Go36-33-临时对象池(sync.Pool)

临时对象池(sync.Pool) sync.Pool是Go语言标准库中的一个同步工具. 介绍 sync.Pool类型可以被称为临时对象池,它的值可以被用来存储临时的对象.它属于结构体类型,在它的值被真正使用之后,就应该再被复制了. 临时对象,就是不需要持久使用的某一类值.这类值对于程序来说可有可无,但如果有的话明显更好.它们的创建和销毁可以在任何时候发生,并且完全不会影响到程序功能.同时,它们也应该是无需被区分的,其中的任何一个值都可以代替另一个.如果某类值完全符合上述条件,就可以把它们存储到临

ZooKeeper原理及使用场景

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等.Zookeeper是hadoop的一个子项目,其发展历程无需赘述.在分布式应用中,由于工程师不能很好地使用锁机制,以及基于消息的协调机制不适合在某些应用中使用,因此需要有一种可靠的.可扩展的.分布式的.可配置的协调机制来统一系统的状态.Zookeeper的目的就在于此.本文简单分析zookeeper的工作原理及使用场景. 一.ZooKeeper角

第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景

在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种数据库自身体系结构的理解.今天这一讲,作为补充 Java 面试考察知识点的完整性,关于数据库的应用和细节还需要在实践中深入学习.今天我要问你的问题是,谈谈 MySQL 支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?典型回答所谓隔离级别(Isolation Level),就是在数据库事务中,

Objective-C Autorelease Pool 的实现原理

内存管理一直是学习 Objective-C 的重点和难点之一,尽管现在已经是 ARC 时代了,但是了解 Objective-C 的内存管理机制仍然是十分必要的.其中,弄清楚 autorelease 的原理更是重中之重,只有理解了 autorelease 的原理,我们才算是真正了解了 Objective-C 的内存管理机制.注:本文使用的 runtime 源码是当前的最新版本 objc4-646.tar.gz . autoreleased 对象什么时候释放 autorelease 本质上就是延迟调

Objective-C Autorelease Pool 的实现原理[转]

http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/ 内存管理一直是学习 Objective-C 的重点和难点之一,尽管现在已经是 ARC 时代了,但是了解 Objective-C 的内存管理机制仍然是十分必要的.其中,弄清楚 autorelease 的原理更是重中之重,只有理解了 autorelease 的原理,我们才算是真正了解了 Objective