golang Pool实战与底层实现

使用的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复制(君子协议), 还学到了一个延迟处理下标的小技巧。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 重要数据类型

目录 列表 序列操作 列表内置方法 列表推到式 字典 声明字典 字典基本操作 列表内置方法 字典进阶使用 字典生成式 附录 列表 在实际开发中&#xff0c;经常需要将一组&#xff08;不只一个&#xff09;数据存储起来&#xff0c;以便后边的代码使用。列表就是这样的…

JNPF低代码平台详解 -- 系统架构

目录 一、技术介绍 技术架构 二、设计原理 三、界面展示 1.代码生成器 2.工作流程 3.门户设计 4.大屏设计 5.报表设计 6.第三方登录 7.多租户实现 8.分布式调度 9.消息中心 四、功能框架 JNPF低代码是一款新奇、实用、高效的企业级软件开发工具&#xff0c;支持企…

柱状展示当中 ,如何给每个位置加多个项的办法

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>双柱修改</title> <script src"https://cdn.staticfile.org/Chart.js/3.9.1/chart.js"></script> </head> <body><canvas i…

分治算法——912. 排序数组

文章目录 &#x1f348;1. 题目&#x1f34c;2. 算法原理&#x1f34f;3. 代码实现 &#x1f348;1. 题目 题目链接&#xff1a;912. 排序数组 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 示例 1&#xff1a; 输入&am…

【Vulnhub 靶场】【Funbox: Lunchbreaker】【简单】【20210522】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/funbox-lunchbreaker,700/ 靶场下载&#xff1a;https://download.vulnhub.com/funbox/FunboxLunchbreaker.ova 靶场难度&#xff1a;简单 发布日期&#xff1a;2021年05月22日 文件大小&#xff1a;1.6 GB 靶…

java: 警告: 源发行版 17 需要目标发行版 17

这是一个编译期的报错提示 源发行版 17 , 即说明你的maven项目当前指定的编译版本是jdk17&#xff0c;需要目标发行版 17则是说明你的idea中实际选择的jdk版本并非17 检查你项目中的pom文件中的配置 <properties><java.version>17</java.version><prop…

localStorage 和sessionStorage

localStorage 和 sessionStorage 是浏览器提供的两种客户端存储数据的方式&#xff1a; 生命周期&#xff1a; localStorage&#xff1a; 存储在 localStorage 中的数据在浏览器关闭后仍然保留&#xff0c;直到被显式删除或浏览器清除缓存。sessionStorage&#xff1a; 存储在 …

2023年12月2日历史上的今天大事件早读

823年12月2日 《门罗宣言》发表 1908年12月2日 末代皇帝溥仪登基 1919年12月2日 平江开展驱除湘督张敬尧运动 1929年12月2日 北平周口店发现中国猿人头盖骨 1941年12月2日 美裔华人物理学家朱经武出生 1949年12月2日 中央决定发行人民胜利公债 1952年12月2日 拿破仑三世成…

mac 系统 vmware 安装centos8

选择镜像 安装系统 依次设置有告警的项目 设置用户名密码 设置root密码 重启系统 重启成功进入下面界面 勾选&#xff0c;点击done 点击箭头所指按钮 输入密码登录 安装成功了 设置网络 打开终端 切换root用户 输入下面指令 su root 输入root的密码 安装git

Python爬虫-新能源汽车销量榜

前言 本文是该专栏的第11篇,后面会持续分享python爬虫案例干货,记得关注。 本文以懂车平台的新能源汽车销量榜单为例,获取各车型的销量排行榜单数据。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。 废话不多说,跟着笔者直接往下看正文详细内容。(附带…

python的制图

测试数据示例&#xff1a; day report_user_cnt report_user_cnt_2 label 2023-10-01 3 3 欺诈 2023-10-02 2 4 欺诈 2023-10-03 6 5 欺诈 2023-10-04 2 1 正常 2023-10-05 4 3 正常 2023-10-06 4 4 正常 2023-10-07 2 6 正常 2023-10-08 3 7 正常 2023-10-09 3 12 正常 2023-…

jetpack compose——圆角、渐变

一、背景圆角、渐变 效果图&#xff1a; 代码为&#xff1a; Box(modifier Modifier.clip(RoundedCornerShape(14.dp)) // 设置圆角半径.background(brush Brush.horizontalGradient( // 设置渐变色listOf(Color(0xFFF5DEC9),Color(0xFFF7A74C),))).constrainAs(box_bottom…

杨志丰:OceanBase助力企业应对数据库转型深水区挑战

11 月 16 日&#xff0c;OceanBase 在北京顺利举办 2023 年度发布会&#xff0c;正式宣布&#xff1a;将持续践行“一体化”产品战略&#xff0c;为关键业务负载打造一体化数据库。OceanBase 产品总经理杨志丰发表了《助力企业应对数据库转型深水区挑战》主题演讲。 以下为演讲…

【el-form】表单label添加?及tooltip

<el-form-item><span slot"label"><el-tooltip :content"tooltip提示框内容" placement"top"><i class"el-icon-question"></i></el-tooltip>{{ $t(menu.status) }}</span><el-radio-gr…

【1】基于多设计模式下的同步异步日志系统

1. 项目介绍 本项⽬主要实现⼀个⽇志系统&#xff0c; 其主要⽀持以下功能: • ⽀持多级别⽇志消息 • ⽀持同步⽇志和异步⽇志 • ⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中 • ⽀持多线程程序并发写⽇志 • ⽀持扩展不同的⽇志落地⽬标地 2. 开发环境 • CentOS 7 • vs…

(C++)有效三角形的个数--双指针法

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://le…

旋转立方体.html(网上收集5)

<!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>旋转立方体</title><style>#cube {width: 200px;height: 200px;position: relative;transform-style: preserve-3d;animation: rotate 6s infinite linear;mar…

开发问题记录

开发问题记录&#x1f4dd; vant组件开发相关问题 vant开发的组件库初次渲染没问题,只要跳转至其他页面再进来设置的label-align属性就会丢失不生效 原因: 进入其他页面加载了主项目的vant库的css优先级层级高,然后就把组件本身的样式覆盖了, ​​​​ 解决思路: 使用渗透把主…

兼容jlink OB arm仿真器使用(杜邦线过长导致烧写总是失败)

一、兼容jlink OB的使用&#xff1a; 1、设置中要选择jlink&#xff1b; 2、模式选择SWD模式&#xff08;接三根线&#xff09;&#xff1b; 二、杜邦线过长导致stm32的stlink烧写总是失败 用ST-link烧写提示的错误信息有&#xff1a; Error while accessing a target reso…

蓝桥杯day03——Bigram 分词

1.题目 给出第一个词 first 和第二个词 second&#xff0c;考虑在某些文本 text 中可能以 "first second third" 形式出现的情况&#xff0c;其中 second 紧随 first 出现&#xff0c;third 紧随 second 出现。 对于每种这样的情况&#xff0c;将第三个词 "th…