然后将新的节点d2和d互相绑定一下,并将d2设值为头节点,将传入的对象push到d2中;
poolDequeue&pushHead func (d *poolDequeue) pushHead(val interface{}) bool { ptrs := atomic.LoadUint64(&d.headTail) // 解包headTail head, tail := d.unpack(ptrs) // 判断队列是否已满 if (tail+uint32(len(d.vals)))&(1<<dequeueBits-1) == head { return false } // 找到head的槽位 slot := &d.vals[head&uint32(len(d.vals)-1)] // 检查slot是否和popTail有冲突 typ := atomic.LoadPointer(&slot.typ) if typ != nil { return false } if val == nil { val = dequeueNil(nil) } // 将 val 赋值到 slot,并将 head 指针值加 1 *(*interface{})(unsafe.Pointer(slot)) = val atomic.AddUint64(&d.headTail, 1<<dequeueBits) return true }首先通过位运算判断队列是否已满,也就是将尾部指针加上 len(d.vals) ,因为head指向的是将要被填充的位置,所以head和tail位置是相隔len(d.vals),然后再取低 31 位,看它是否和 head 相等。如果队列满了,直接返回 false;
然后找到找到head的槽位slot,并判断typ是否为空,因为popTail 是先设置 val,再将 typ 设置为 nil,所以如果有冲突,那么直接返回;
最后设值slot,并将head加1返回;
GC在pool.go文件的 init 函数里,注册了 GC 发生时,如何清理 Pool 的函数:
func init() { runtime_registerPoolCleanup(poolCleanup) } func poolCleanup() { for _, p := range oldPools { p.victim = nil p.victimSize = 0 } for _, p := range allPools { p.victim = p.local p.victimSize = p.localSize p.local = nil p.localSize = 0 } oldPools, allPools = allPools, nil }poolCleanup 会在 STW 阶段被调用。主要是将 local 和 victim 作交换,那么不至于GC 把所有的 Pool 都清空了,而是需要两个 GC 周期才会被释放。如果 sync.Pool 的获取、释放速度稳定,那么就不会有新的池对象进行分配。
总结Pool这个概念在后台优化中是一个非常重要的手段,比如说在使用Http的时候会使用Http连接池,使用数据库的时候,也会用到数据库连接池。这些通过对象重用和预先分配可以减少服务器的压力。
当我们在后期的项目开发中,如果发现GC耗时很高,有大量临时对象时不妨可以考虑使用Pool。
例如发现现系统中的 goroutine 数量非常多,由于一个goroutine初始栈是2048字节,所以一个服务器上运行数十万的goroutine 也是非常耗时的;这时候就可以考虑使用Worker Pool 来减少 goroutine 的使用。
Referencehttps://medium.com/@genchilu/whats-false-sharing-and-how-to-solve-it-using-golang-as-example-ef978a305e10
https://zhuanlan.zhihu.com/p/99710992
https://morsmachine.dk/go-scheduler