golang Pool实战与底层实现

news2024/10/5 17:47:44

使用的go版本为 go1.21.2

首先我们写一个简单的Pool的使用代码

package main

import "sync"

var bytePool = sync.Pool{
	New: func() interface{} {
		b := make([]byte, 1024)
		return &b
	},
}

func main() {
	for j := 0; j < 10; j++ {
		obj := bytePool.Get().(*[]byte) // 获取一个[]byte
		_ = obj
		bytePool.Put(obj) // 用完再给放回去
	}
}

pool对象池的作用

  1. 减少内存分配: 通过池,可以减少对内存的频繁分配和释放,提高程序的内存利用率。
  2. 避免垃圾回收压力: 对象池中的对象在被使用后不会立即被释放,而是放回到池中等待复用。这有助于减轻垃圾回收的压力,因为对象可以在多次使用后才被真正释放。
  3. 提高性能: 复用对象可以避免不必要的对象创建和销毁开销,从而提高程序的性能。
    从demo上看好像没啥卵用,我们来进行一些压力测试
package main

import (
	"sync"
	"testing"
)

var bytePool = sync.Pool{
	New: func() interface{} {
		b := make([]byte, 1024)
		return &b
	},
}

func BenchmarkByteMake(b *testing.B) {
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for j := 0; j < 10000; j++ {
			obj := make([]byte, 1024)
			_ = obj
		}
	}
}

func BenchmarkBytePool(b *testing.B) {
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for j := 0; j < 10000; j++ {
			obj := bytePool.Get().(*[]byte) // 获取一个1024长度的[]byte
			_ = obj
			bytePool.Put(obj) // 用完再给放回去
		}
	}
}

看一下压测效果

可以看到执行效率高了好多倍

项目中没实际用到过,不过我们可以翻一下开源项目中是怎么用的

redis-v9 

 

Pool结构体

比较复杂有点套娃的意思

//代码位于 GOROOT/src/sync/pool.go L:49
type Pool struct {
  //防止Pool被复制, 君子协议,编译可以通过,某些编辑器会报waring
  //静态检测 go vet会出错
  //有兴趣可以看一下这里 https://github.com/golang/go/issues/8005#issuecomment-190753527
	noCopy noCopy

	local     unsafe.Pointer // 本地池,对应类型[P]poolLocal P指的是 GMP中的P.ID字段
	localSize uintptr        // 本地池大小

	victim     unsafe.Pointer // 上一个周期的本地池
	victimSize uintptr        // 上一个周期的本地池大小


	New func() any // 创建对象的方法,这个需要我们自己实现
}

type poolLocal struct { //本地池
	poolLocalInternal

	// 用128取模,确保结构体占据整数个缓存行,从而防止伪共享.
	pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}

type poolLocalInternal struct {
	private interface{} // 本地P的私有字段
	shared  poolChain   // 双端链表, 任何P都可以进行popTail
}


//代码位于 GOROOT/src/sync/poolqueue.go L:194
type poolChain struct { // 双向队列
	//头部
	head *poolChainElt

	//尾部
	tail *poolChainElt
}


type poolChainElt struct { //环状队列
	poolDequeue 

	// next 由生产者原子性地写入,并由消费者原子性地读取, 从非nil转换为nil
	// prev 由消费者原子性地写入,并由生产者原子性地读取, 从非nil转换为nil
	next, prev *poolChainElt
}

//代码位于 GOROOT/src/sync/poolqueue.go L:19
type poolDequeue struct {
	//一个字段两个含义,高32位为头,低32位为尾部
	headTail uint64

	//环形缓存
	//vals[i].typ 为nil 说明该槽位为空
	vals []eface
}

type eface struct { //类型与值
	typ, val unsafe.Pointer
}

Get函数

//代码位于 GOROOT/src/sync/pool.go L:127
func (p *Pool) Get() any {
	if race.Enabled { // 使用竞态检查
		race.Disable() //竞态检查 禁用
	}
	l, pid := p.pin() //获取当前P的ID 与 poolLocal 详细见下方
	x := l.private //看看私有属性是否存在对象,如果存在就可以直接返回
	l.private = nil
	if x == nil { //
		//优先从链表的头部获取,
		x, _ = l.shared.popHead()
		if x == nil {// 慢读取路径
			x = p.getSlow(pid)
		}
	}
	runtime_procUnpin() //取消 P 的禁止抢占
	if race.Enabled { // 使用竞态检查
		race.Enable() //竞态检查 启用
		if x != nil {
			race.Acquire(poolRaceAddr(x))
		}
	}
	if x == nil && p.New != nil { //调度new方法重新生成一个对象
		x = p.New()
	}
	return x
}
pin函数
 
//代码位于 GOROOT/src/sync/pool.go L:127
func (p *Pool) pin() (*poolLocal, int) {
	//获取P的id
	pid := runtime_procPin()
	// 原子操作获取本地池大小
	// 本地池
	s := runtime_LoadAcquintptr(&p.localSize) // load-acquire
	l := p.local                              // load-consume
	if uintptr(pid) < s { //如果当前P.id 没有在local中越界,直接去获取值
		return indexLocal(l, pid), pid
	}
	return p.pinSlow() //慢获取
}

func (p *Pool) pinSlow() (*poolLocal, int) {
	//取消P的禁止抢占
	runtime_procUnpin()
	allPoolsMu.Lock() //加锁
	defer allPoolsMu.Unlock()
	pid := runtime_procPin() //获取P的id
	//获取本地池的大小与本地池
	s := p.localSize
	l := p.local
	if uintptr(pid) < s { //如果当前P.id 没有在local中越界,直接去获取值
		return indexLocal(l, pid), pid
	}
	if p.local == nil { //如果local为空,将他加入到allPools中
		allPools = append(allPools, p)
	}
	// GOMAXPROCS在GC之间发送了变化,重新分配p.load与p.localSize
	size := runtime.GOMAXPROCS(0)
	local := make([]poolLocal, size)
	atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
	runtime_StoreReluintptr(&p.localSize, uintptr(size))     // store-release
	return &local[pid], pid
}

getSlow函数
//代码位于 GOROOT/src/sync/pool.go L:156
func (p *Pool) getSlow(pid int) any {
	// 原子获取本地池大小
	// 本地池
	size := runtime_LoadAcquintptr(&p.localSize) // load-acquire
	locals := p.local                            // load-consume
	// 尝试从别的P poolLocal尾部获取local
	// 这个循环的方式有点东西(pid+i+1)%int(size),优先从非pid的下标获取,最后一次是pid
	for i := 0; i < int(size); i++ {
		l := indexLocal(locals, (pid+i+1)%int(size))
		if x, _ := l.shared.popTail(); x != nil {
			return x
		}
	}

	// 原子获取上一周期本地池大小
	size = atomic.LoadUintptr(&p.victimSize)
	if uintptr(pid) >= size { //如果pid大于size 说明让回收掉了
		return nil
	}
	locals = p.victim
	l := indexLocal(locals, pid)
	if x := l.private; x != nil {//看看私有属性是否存在对象,如果存在就可以直接返回
		l.private = nil
		return x
	}
	// 尝试从别的P poolLocal尾部获取local
	for i := 0; i < int(size); i++ {
		l := indexLocal(locals, (pid+i)%int(size))
		if x, _ := l.shared.popTail(); x != nil {
			return x
		}
	}

	//将victimSize设置为0
	atomic.StoreUintptr(&p.victimSize, 0)

	return nil
}

 

Put函数

//代码位于 GOROOT/src/sync/pool.go L:95
func (p *Pool) Put(x any) {
	if x == nil { //如果写入的x为nil之间返回
		return
	}
	if race.Enabled { //使用竞态检查
		if fastrandn(4) == 0 {
			// Randomly drop x on floor.
			return
		}
		race.ReleaseMerge(poolRaceAddr(x))
		race.Disable() // 竞态检查 禁用
	}
	l, _ := p.pin() // 获取PoolLocal
	if l.private == nil { // 如果私有属性没有赋值
		l.private = x
	} else { //将x写入头
		l.shared.pushHead(x)
	}
	runtime_procUnpin()
	if race.Enabled { //使用竞态检查
		race.Enable() //竞态检查 启用
	}
}

pushHead函数解读
//代码位于 GOROOT/src/sync/poolqueue.go L:228
func (c *poolChain) pushHead(val any) {
	d := c.head
	if d == nil { //如果head为空,将head初始化为8长度的eface数组
		const initSize = 8 // Must be a power of 2
		d = new(poolChainElt)
		d.vals = make([]eface, initSize)
		c.head = d
		storePoolChainElt(&c.tail, d) //将新创建的节点,当做尾节点
	}

	if d.pushHead(val) { //对象入队
		return
	}

	// 走到这里说明满了。可扩容为2倍
	newSize := len(d.vals) * 2
	// 扩容大小 (1 << 32) / 4 超出将这个设置为(1 << 32) / 4
	if newSize >= dequeueLimit { 
		newSize = dequeueLimit
	}
	//新建poolChainElt将prev指向d
	d2 := &poolChainElt{prev: d}
	d2.vals = make([]eface, newSize)
	c.head = d2 //将新创建的节点,当做头节点
	storePoolChainElt(&d.next, d2) // 将老的节点指向,新节点
	d2.pushHead(val) //对象入队
}

 

延迟处理下标小技巧

package main

import (
	"fmt"
)

func main() {
	pid := 1
	size := 20

	for i := 0; i < int(size); i++ {
		if i == pid {
			continue
		}
		fmt.Println(i)
	}

	// 优化版本 pid会在最后一个打印处理
	for i := 0; i < size; i++ {
		index := (pid + i + 1) % size
          // 前面处理完以后直接return
		fmt.Println(index)
	}
}

总结

我们从上面的源码分析了解Pool的数据结构、Get、Put这些基本操作原理,在项目中我们可以使用比特位来减少内存的占用,从源码分析我们得知Go官方设计不允许进行Pool复制(君子协议), 还学到了一个延迟处理下标的小技巧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1276928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决element ui tree组件不产生横向滚动条

结果是这样的 需要在tree的外层&#xff0c;包一个父组件 <div class"tree"><el-tree :data"treeData" show-checkbox default-expand-all></el-tree></div> 在css里面这样写,样式穿透按自己使用的css编译器以及框架要求就好 &l…

SQL Server 2016(创建数据库)

1、实验环境。 某公司有一台已经安装了SQL Server 2016的服务器&#xff0c;现在需要新建数据库。 2、需求描述。 创建一个名为"db_class"的数据库&#xff0c;数据文件和日志文件初始大小设置为10MB&#xff0c;启用自动增长&#xff0c;数据库文件存放路径为C:\db…

文献速递:人工智能在健康和医学中

人工智能在健康和医学中 01 文献速递介绍 这篇文章详细探讨了人工智能&#xff08;AI&#xff09;在医学领域的最新进展、挑战和未来发展的机遇。 1.医学AI算法的最新进展&#xff1a; **AI在医疗实践中的应用&#xff1a;**虽然AI系统在多项回顾性医学研究中表现出色&…

docker 搭建开发环境,解决deepin依赖问题

本机环境&#xff1a; deepin v23b2 删除docker旧包 sudo apt-get remove docker docker-engine docker.io containerd runc注意卸载docker旧包的时候Images, containers, volumes, 和networks 都保存在 /var/lib/docker 卸载的时候不会自动删除这块数据&#xff0c;如果你先…

Beautiful Soup4爬虫速成

做毕业论文需要收集数据集&#xff0c;我的数据集就是文本的格式&#xff0c;而且是静态页面的形式&#xff0c;所以只是一个简单的入门。动态页面的爬虫提取这些比较进阶的内容&#xff0c;我暂时没有这样的需求&#xff0c;所以有这类问题的朋友们请移步。 如果只是简单的静态…

目标检测——Faster R-CNN算法解读

论文&#xff1a;Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks 作者&#xff1a;Shaoqing Ren, Kaiming He, Ross Girshick, and Jian Sun 链接&#xff1a;https://arxiv.org/abs/1506.01497 代码&#xff1a;https://github.com/rbgirsh…

vue使用elementui的el-menu的折叠菜单collapse

由于我的是在el-menu所在组件外面的兄弟组件设置是否折叠的控制&#xff0c;我用事件总线bus进行是否折叠传递 参数说明类型可选值默认值collapse是否水平折叠收起菜单&#xff08;仅在 mode 为 vertical 时可用&#xff09;boolean—falsebackground-color菜单的背景色&#…

深入理解Servlet(上)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 为什么要了解Servlet …

用JavaScript的管道方法简化代码复杂性

用JavaScript的管道方法简化代码复杂性 在现代 web 开发中&#xff0c;维护干净有效的代码是必不可少的。随着项目的增加&#xff0c;我们功能的复杂性也在增加。然而&#xff0c;javaScript为我们提供了一个强大的工具&#xff0c;可以将这些复杂的函数分解为更小的、可管理的…

什么是Anaconda

Anaconda的安装也很方便。打开这个网站Anaconda下载&#xff0c;然后安装即可。 Anaconda可以帮助我们解决团队之间合作的包依赖管理问题。在没有使用Anaconda之前&#xff0c;如果你的Python程序想让你的同事运行&#xff0c;那么你的同事可能会遇到很多包依赖问题&#xff0…

【 RTTI 】

RTTI 概念&#xff1a; RTTI(Run Time Type Identification)即通过运行时类型识别&#xff0c;程序能够使用基类的指针或引用来检 查着这些指针或引用所指的对象的实际派生类型。 原因&#xff1a; C是一种静态类 型语言。其数据类型是在编译期就确定的&#xff0c;不能在运…

2023年中国消费金融行业研究报告

第一章 行业概况 1.1 定义 中国消费金融行业&#xff0c;作为国家金融体系的重要组成部分&#xff0c;旨在为消费者提供多样化的金融产品和服务&#xff0c;以满足其消费需求。这一行业包括银行、消费金融公司、小额贷款公司等多种金融机构&#xff0c;涵盖了包括消费贷款在内…

力扣15题 三数之和 双指针算法

15. 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三…

mysql的InnoDB存储引擎

详情请参考&#xff1a;https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html InnoDB 是一个通用目的的存储引擎&#xff0c;它在高可用性、高性能方面做了平衡。MySQL 8.0&#xff0c;InnoDB 是默认的存储引擎。在创建表的时候&#xff0c;如果没有使用ENGIN…

1+x网络系统建设与运维(中级)-练习题

一.给设备重命名 同理可得&#xff0c;所有交换机和路由器都用一下命令配置 <Huawei>sys [Huawei]sysn LSW1 二.配置VLAN LSW1&#xff1a; [LSW1]vlan batch 10 20 [LSW1]int e0/0/1 [LSW1-Ethernet0/0/1]port link-type access [LSW1-Ethernet0/0/1]port default vlan…

用户反馈组件实现(Vue3+ElementPlus)含图片拖拽上传

用户反馈组件实现&#xff08;Vue3ElementPlus&#xff09;含图片拖拽上传 1. 页面效果1.1 正常展示1.2 鼠标悬浮1.3 表单 2. 代码部分1.2 html、ts1.2 less部分 3. 编码过程遇到的问题 1. 页面效果 1.1 正常展示 1.2 鼠标悬浮 1.3 表单 2. 代码部分 1.2 html、ts <templ…

gorm修改操作中两个update方法的小细节

在使用gorm进行修改操作时&#xff0c;修改操作中如下两个方法&#xff1a; Update() Updates() 都可以实现修改&#xff0c;根据名称可以看出Update是针对单个字段&#xff0c;而后者应该是多个。 下面是主要实际操作&#xff1a; ​​ Updates() 即&#xff0c;前者确实是…

vector是如何扩容的

vector容器扩容 vector是成倍扩容的&#xff0c;一般是2倍。 vector管理内存的成员函数 开始填值 没有填值之前&#xff0c;vector元素个数和容量大小都为0 加入一个值之后&#xff1a; 加入两个值&#xff1a;重点在加入三个值&#xff0c;此时容量变为4&#xff1a;加入第…

开源图床Qchan本地部署远程访问,轻松打造个人专属轻量级图床

文章目录 前言1. Qchan网站搭建1.1 Qchan下载和安装1.2 Qchan网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar云端设置2.2 Cpolar本地设置 3. 公网访问测试总结 前言 图床作为云存储的一项重要应用场景&#xff0c;在大量开发人员的努力下&#xff0c;已经开发出大…

优彩云采集器最新版免费下载,优彩云采集器免费

随着网络时代的发展&#xff0c;SEO&#xff08;Search Engine Optimization&#xff0c;搜索引擎优化&#xff09;已经成为网站推广和营销的关键一环。在SEO的世界里&#xff0c;原创内容的重要性愈发凸显。想要做到每天更新大量原创文章&#xff0c;并不是一件轻松的事情。优…