在go http每一次go serve(l)都会构建Request数据结构.在大量数据请求或高并发的场景中,频繁创建销毁对象,会导致GC压力.解决办法之一就是使用对象复用技术.在http协议层之下,使用对象复用技术创建Request数据结构.在http协议层之上,可以使用对象复用技术创建(w,*r,ctx)数据结构.这样即可以回快TCP层读包之后的解析速度,也可也加快请求处理的速度.
先上一个测试:
结论是这样的:
貌似使用池化,性能弱爆了?这似乎与net/http使用sync.pool池化Request来优化性能的选择相违背.这同时也说明了一个问题,好的东西,如果滥用反而造成了性能成倍的下降.在看过pool原理之后,结合实例,将给出正确的使用方法,并给出预期的效果.
sync.Pool是一个 协程安全 的 临时对象池 .数据结构如下:
local 成员的真实类型是一个 poolLocal 数组,localSize 是数组长度.这涉及到Pool实现,pool为每个P分配了一个对象,P数量设置为runtime.GOMAXPROCS(0).在并发读写时,goroutine绑定的P有对象,先用自己的,没有去偷其它P的.go语言将数据分散在了各个真正运行的P中,降低了锁竞争,提高了并发能力.
不要习惯性地误认为New是一个关键字,这里的New是Pool的一个字段,也是一个闭包名称.其API:
如果不指定New字段,对象池为空时会返回nil,而不是一个新构建的对象.Get()到的对象是随机的.
原生sync.Pool的问题是,Pool中的对象会被GC清理掉,这使得sync.Pool只适合做简单地对象池,不适合作连接池.
pool创建时不能指定大小,没有数量限制.pool中对象会被GC清掉,只存在于两次GC之间.实现是pool的init方法注册了一个poolCleanup()函数,这个方法在GC之前执行,清空pool中的所有缓存对象.
注意到pool中的对象是无差异性的,加锁或者分段加锁都不是较好的做法.go的做法是为每一个绑定协程的P都分配一个子池.每个子池又分为私有池和共享列表.共享列表是分别存放在各个P之上的共享区域,而不是各个P共享的一块内存.协程拿自己P里的子池对象不需要加锁,拿共享列表中的就需要加锁了.
Get对象过程:
Put过程:
如何解决Get最坏情况遍历所有P才获取得对象呢:
执行结果:
单线程情况下,遍历其它无元素的P,长时间加锁性能低下.启用协程改善.
结果:
测试场景在goroutines远大于GOMAXPROCS情况下,与非池化性能差异巨大.
测试结果
可以看到同样使用*sync.pool,较大池大小的命中率较高,性能远高于空池.
池pool和缓存cache的区别.池的意思是,池内对象是可以互换的,不关心具体值,甚至不需要区分是新建的还是从池中拿出的.缓存指的是KV映射,缓存里的值互不相同,清除机制更为复杂.缓存清除算法如LRU、LIRS缓存算法.
池空间回收的几种方式.一些是GC前回收,一些是基于时钟或弱引用回收.最终确定在GC时回收Pool内对象,即不回避GC.用java的GC解释弱引用.GC的四种引用:强引用、弱引用、软引用、虚引用.虚引用即没有引用,弱引用GC但有空间则保留,软引用GC即清除.ThreadLocal的值为弱引用的例子.
regexp 包为了保证并发时使用同一个正则,而维护了一组状态机.
fmt包做字串拼接,从sync.pool拿[]byte对象.避免频繁构建再GC效率高很多.
正如sycn.Pool的名字所示,这是go中实现的一个对象池,为什么要有这个池呢?首先go是自带垃圾回收机制(也就是通常所说的gc).gc会带来运行时的开销,对于高频的内存申请与释放,如果将不用的对象存放在一个池子中,用的时候从池子中取出一个对象,用完了再还回去,这样就能减轻gc的压力.
对于池这个概念,之前可能听说过连接池.能否用sync.Pool实现一个连接池呢?答案是不能的.因为对于sync.Pool而言,我们无法保证每次放回去再取出来的对象是与之前一致的,对象的内存存在着呗销毁的可能.所以呢,这个sync.Pool的存在仅仅是为了减缓gc的压力而生的.
定义sync.Pool的时候只需要设置一个New成员,它是一个函数,类型为func() interface{},当池子中没有空闲的对象时就会调用New函数生成一个.由于pool中对象的数量不可控,所以呢并没有传递任何与对象数量有关的参数.
然后,调用调用Get函数就可以取出一个对象,调用Put函数就可以将对象归还到池子中.
①在创建连接池之后,起一个 go routine,每隔一段 idleTime 发送一个 PING 到 Redis server.其中,idleTime 略小于 Redis server 的 timeout 配置.
p, err := pool.New("tcp", u.Host, concurrency) errHndlr(err) go func() { for { p.Cmd("PING") time.Sleep(idelTime * time.Second) } }()
func redisDo(p *pool.Pool, cmd string, args ...interface{}) (reply *redis.Resp, err error) { reply = p.Cmd(cmd, args...) if err = reply.Err; err != nil { if err != io.EOF { Fatal.Println("redis", cmd, args, "err is", err) } } return }
// Cmd automatically gets one client from the pool, executes the given command // (returning its result), and puts the client back in the pool func (p *Pool) Cmd(cmd string, args ...interface{}) *redis.Resp { c, err := p.Get() if err != nil { return redis.NewResp(err) } defer p.Put(c) return c.Cmd(cmd, args...) }
这样,就有了系统 keep alive 的机制,不会出现 time out 的连接了,从 redis 连接池里面取出的连接都是可用的连接了.看似简单的代码,却完美的解决了连接池里面超时连接的问题.同时,就算 Redis server 重启等情况,也能保证连接自动重连.
以上就是土嘎嘎小编为大家整理的go语言pool相关主题介绍,如果您觉得小编更新的文章只要能对粉丝们有用,就是我们最大的鼓励和动力,不要忘记讲本站分享给您身边的朋友哦!!