[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
[Go] sync.Pool 的实现原理 和 适用场景的更多相关文章
- 深入Golang之sync.Pool详解
我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池. sync.Pool ...
- 深度解密 Go 语言之 sync.Pool
最近在工作中碰到了 GC 的问题:项目中大量重复地创建许多对象,造成 GC 的工作量巨大,CPU 频繁掉底.准备使用 sync.Pool 来缓存对象,减轻 GC 的消耗.为了用起来更顺畅,我特地研究了 ...
- 深入理解 sync.Once 与 sync.Pool
深入理解 sync.Once 与 sync.Pool sync.Once 代表在这个对象下在这个示例下多次执行能保证只会执行一次操作. var once sync.Once for i:=0; i & ...
- sync.Pool的使用
一定要搞明白sync.Pool的正确用法,避免出现以下问题: kline := this.pool.Get() defer this.pool.Put(kline) kline.UnMarshal(d ...
- go语言学习--go的临时对象池--sync.Pool
一个sync.Pool对象就是一组临时对象的集合.Pool是协程安全的. Pool用于存储那些被分配了但是没有被使用,而未来可能会使用的值,以减小垃圾回收的压力.一个比较好的例子是fmt包,fmt包总 ...
- golang sync.Pool包的使用和一些注意地方
package main; import ( "sync" "fmt" "net" "runtime" ) //sync ...
- sync.Pool 资源池
sync.Pool type Pool struct { // 可选参数New指定一个函数在Get方法可能返回nil时来生成一个值 // 该参数不能在调用Get方法时被修改 New func() in ...
- Golang 临时对象池 sync.Pool
Go 1.3 的sync包中加入一个新特性:Pool.官方文档可以看这里http://golang.org/pkg/sync/#Pool 这个类设计的目的是用来保存和复用临时对象,以减少内存分配,降低 ...
- go的临时对象池--sync.Pool
作者:bigtom链接:https://www.jianshu.com/p/2bd41a8f2254來源:简书 一个sync.Pool对象就是一组临时对象的集合.Pool是协程安全的. Pool用 ...
随机推荐
- [转]Restrict关键字
0 定义 C99中新增加的用于修饰指针的关键字,用于表示该指针所指向的内存,只有通过该指针访问得到(如下ptr指向的内存单元只能通过ptr访问得到).从而可以让编译器对代码进行优化,生成更有效率的汇编 ...
- Linux 抽象网络设备简介
Linux 抽象网络设备简介 和磁盘设备类似,Linux 用户想要使用网络功能,不能通过直接操作硬件完成,而需要直接或间接的操作一个 Linux 为我们抽象出来的设备,既通用的 Linux 网络设备来 ...
- memcache 键名的命名规则以及和memcached的区别
2014年3月27日 07:47:46 Keys---- Data stored by memcached is identified with the help of a key. A keyis ...
- System.Web.Routing入门及进阶 上篇
System.Web.Routing已经作为一个程序集包含在.net3.5sp1中发布了.虽然我们并没有在3.5sp1中发现Asp.net Mvc的踪迹,但是亦以感觉到它离我们不远了. System. ...
- poj2148
题意:给出若干个没有公共面积的多边形,几个多边形可能属于同一个国家,要求给这个地图染色,同一个国家用相同的颜色,相邻国家不能用相同颜色.问最少需要多少种颜色. 分析:计算几何+搜索.先判断哪些多边形是 ...
- 【前端node.js框架】node.js框架express
server.js /* 以下代码等下会有详细的解释 */ var express = require('express'); // 用来引入express模块 var app = express() ...
- Windows 安装 Go语言开发环境以及使用
下载安装包 下载地址:http://www.golangtc.com/download 32 位请选择名称中包含 windows-386 的 msi 安装包,64 位请选择名称中包含 windows- ...
- django startproject xxx:报错UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 13: ordinal not in range(128)
django startproject xxx:报错UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 13: o ...
- Java编程的逻辑 (62) - 神奇的序列化
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- 《深入分析JavaWeb技术内幕》学习笔记
第一章 深入Web请求过程 1.1 B/S网站架构概述 HTTP协议采用无状态的短连接的通信方式.通常一次请求就完成一次数据交互,通常也对应一个业务逻辑. 当在浏览器里输入一个URL,首先会请求DNS ...