sync.Pool设计分析

golang sync.Pool的数据模型在1.13中发生了比较大的改变,一方面是数据模型的重构,一方面是GC对sync.Pool池子影响的优化。

1.数据模型:

初始情况

  • 默认大小为8,能放8个item。

  • 因为是有限大小的FIFO,所以采取了最佳模型ring buffer来实现这个队列。(因为定长队列是用定长数组实现的,如果在头部push的话,需要后续所有内容往后迁移,复杂度为O(n),而使用环状的话则复杂度为O(1)。)

如图:

(1)O(n)——普通队列


普通队列

(2)O(1)——环状队列


环状队列

https://en.wikipedia.org/wiki/Circular_buffer

  • head端只能一个producer,即当前线程,进行push和pop,tail端可以被多个comsumer,即其他所有线程,进行pop操作。(多个comsumer并发读取采用的是atomic加锁的方式限制,如每次pop时对index进行atomic.CompareAndSwapUint64操作,比较轻量)。

  • tail的每次读取都会删除该项,将这一项变成nil。(也就是说如果读取的速度足够快,则始终就在这大小为8的环里运行,节省空间)

  • head的每次写入val,即使是nil,也会转变为*struct{}作为标记。

初始

如果写满了

  • 如果写的速度比较快,写满了之后,则根据双向链表的方式,引出一个2倍大小的ring buffer(每次都会翻倍,直到达到最大为1<<32 / 4则不再增加)。
  • 当前的ring如果pop head用完了,则会根据prev查看上一个ring
  • 如果tail pop完了之后,则会删除这个ring,随后指向新的ring(因为后续不再使用)
写满

sync.Pool1.13模型设计亮点:

  1. 使用了ring buffer(定长FIFO) + 双向链表的方式,头部只能写入,尾部可以并发读取,相较于1.12版一个数组,加上一个大锁限制,轻量的多,效率高了许多。
  2. head与tail的index使用一个uint64存储,前32位是head,后32位是tail。通过掩码以及位运算获取他俩,然后在atomic.CompareAndSwapUint64的操作中效率更高,一个操作就能比较head和tail的index是否发生了改变。

2. GC优化

https://medium.com/a-journey-with-go/go-understand-the-design-of-sync-pool-2dde3024e277

之前每次GC时都会清空pool,而在1.13版本中引入了victim cache,会将pool内数据拷贝一份,避免GC将其清空,即使没有引用的内容也可以保留最多两轮GC。

相关性能测试:

https://www.jianshu.com/p/cf3ac244e222

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容