go sync.Pool 深入

2023-05-16

new函数的调用时机和pool的内存释放规则

以下代码调用了四次Get函数,但是并不是每次都会new

  • 第一次,是a := pool.Get().([]byte),首次Get,在pool的private私有池没有对象,在共享池也没有对象,不存在victim cache,所以会new。
  • 第二次,是b := pool.Get().([]byte),因为a取出后,pool的私有池又成为了空。在共享池也没有对象,不存在victim cache,所以会new。
  • 第三次,是c := pool.Get().([]byte),理论上:再次取 a,不会执行new,此时victim cache对象。 但是实际上,此处并不确定,有时victim cache的私有池还保留对象,有时已经为空
  • 第四次,一定会执行new,因为经过第一次gc,主缓存清空,第二次gc,victim缓存清空。池中没有对象所以一定会new。

总结:

  1. 当pool中私有池有对象,不进行new,而是返回私有池对象。
  2. 当pool中私有池没有对象,共享池有对象,则返回共享池对象的最前一个。
  3. 当pool中私有池没有对象,共享池也没有对象,则尝试窃取其它P的共享池对象。
  4. 当窃取也窃取不到,则尝试使用victim缓存,再执行1.2.3.4.步骤
  5. 当victim缓存也没有时,会执行new。
  6. pool主缓存中的对象会在GC时移到victim缓存,而此处gc中pool的victim缓存中的对象会在下次gc时被释放。
  7. Put时,如果私有池已经存在对象,则放到共享池,否则放到私有池中
  8. 从私有池中取对象是协程安全的,而从共享池取对象需要加锁,这是因为存在其它P来窃取本P的共享池的现象。

func Test_1(t *testing.T) {
	pool := sync.Pool{New: func() interface{} {
		fmt.Println("new")
		return make([]byte, 2 << 10)
	}}
	fmt.Println("start")
	a := pool.Get().([]byte)
	gcStats := debug.GCStats{}
	runtime.GC()
	debug.ReadGCStats(&gcStats)
	fmt.Printf("numgc: %d\n", gcStats.NumGC)
	for i := range a {
		a[i] = 1
	}
	// 因为上面的a没有put回去,所以此处会重新new
	b := pool.Get().([]byte)
	fmt.Println(b[0]) // all 0

	// 重新放回 a
	pool.Put(a)
	fmt.Println("a == nil :", a==nil)

	// gc释放,pool中引用会被移到victim cache
	runtime.GC()
	debug.ReadGCStats(&gcStats)
	fmt.Printf("numgc: %d\n", gcStats.NumGC)
	fmt.Println("a == nil :", a==nil)

	// 理论上:再次取 a,不会执行new,此时victim cache还有
	// 但是实际上,此处并不确定,有时victim cache的私有池还保留对象,有时已经为空
	c := pool.Get().([]byte)
	fmt.Println(c[0])
	pool.Put(c)

	runtime.GC()
	runtime.GC()
	debug.ReadGCStats(&gcStats)
	fmt.Printf("numgc: %d\n", gcStats.NumGC)
	// 连着GC两次, pool中victim cache也被清空,会执行 new
	pool.Get()
}

Get源码:

// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
方法从池中选择任意项,然后将其从池移除,并将其返回给调用者。
Get可以选择忽略池并将其视为空的。
调用者不应该假定传递给Put和Get的值之间有任何关系。
如果Get返回nil,而p.New是非nil,则Get返回调用p.New的结果。
func (p *Pool) Get() interface{} {
	if race.Enabled {
		race.Disable()
	}
	l, pid := p.pin()
	x := l.private  // x = 私有池对象
	l.private = nil // 释放私有池对象
	if x == nil {   // 如果私有池没有对象,则尝试从共享池找
		// Try to pop the head of the local shard. We prefer
		// the head over the tail for temporal locality of
		// reuse.
		// 试着推出本地堆的头部对象。对于重用的时间位置,我们更喜欢头部而不是尾部。
		x, _ = l.shared.popHead()
		if x == nil {          // 如果本地的共享池没有,会尝试从其它协程的共享池偷取,如果没偷取到,则尝试取victim缓存
			x = p.getSlow(pid)
		}
	}
	runtime_procUnpin()
	if race.Enabled {
		race.Enable()
		if x != nil {
			race.Acquire(poolRaceAddr(x))
		}
	}
	if x == nil && p.New != nil { // 如果共享用池也没有,则执行new
		x = p.New()
	}
	return x
}

func (p *Pool) getSlow(pid int) interface{} {
	// See the comment in pin regarding ordering of the loads.
	size := runtime_LoadAcquintptr(&p.localSize) // load-acquire
	locals := p.local                            // load-consume
	// Try to steal one element from other procs.
        // 尝试从其它p偷取对象
	for i := 0; i < int(size); i++ {
		l := indexLocal(locals, (pid+i+1)%int(size))
		if x, _ := l.shared.popTail(); x != nil {
			return x
		}
	}

	// Try the victim cache. We do this after attempting to steal
	// from all primary caches because we want objects in the
	// victim cache to age out if at all possible.
        // 尝试从victim cache取对象
	size = atomic.LoadUintptr(&p.victimSize)
	if uintptr(pid) >= size {
		return nil
	}
	locals = p.victim
	l := indexLocal(locals, pid)
	if x := l.private; x != nil {
		l.private = nil
		return x
	}
	for i := 0; i < int(size); i++ {
		l := indexLocal(locals, (pid+i)%int(size))
		if x, _ := l.shared.popTail(); x != nil {
			return x
		}
	}

	// Mark the victim cache as empty for future gets don't bother
	// with it.
	// 标记victim cache为空
	atomic.StoreUintptr(&p.victimSize, 0)

	return nil
}

Put源码

// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	if race.Enabled {
		if fastrand()%4 == 0 {
			// Randomly drop x on floor.
			return
		}
		race.ReleaseMerge(poolRaceAddr(x))
		race.Disable()
	}
	l, _ := p.pin()
	if l.private == nil {      // 如果私有池为空,则将对象放到私有池
		l.private = x
		x = nil
	}
	if x != nil {              // 如果私有池不为空,则放到共享池
		l.shared.pushHead(x)
	}
	runtime_procUnpin()
	if race.Enabled {
		race.Enable()
	}
}

Gc 清除pool源码

func poolCleanup() {
	// This function is called with the world stopped, at the beginning of a garbage collection.
	// It must not allocate and probably should not call any runtime functions.

	// Because the world is stopped, no pool user can be in a
	// pinned section (in effect, this has all Ps pinned).
	// 这个函数会在GC开始时STW的时候运行。

	// Drop victim caches from all pools.
	// 将所有victim的对象释放,这些对象会在此处垃圾回收中回收掉
	for _, p := range oldPools {
		p.victim = nil
		p.victimSize = 0
	}

	// Move primary cache to victim cache.
	// 将所有的pool中的local对象放入victim中,以让下次gc回收
	for _, p := range allPools {
		p.victim = p.local
		p.victimSize = p.localSize
		p.local = nil
		p.localSize = 0
	}

	// The pools with non-empty primary caches now have non-empty
	// victim caches and no pools have primary caches.
	// 具有非空主缓存的池现在具有非空的受害者缓存,并且没有池具有主缓存。
	// 主缓存都被回收了
	oldPools, allPools = allPools, nil
}

不要认为Put进去的对象就是下次Get到的对象

  1. 有可能因为GC释放,导致pool清空,会重新new对象
  2. 有可能本P的池为空,从其它P窃取了对象,而不是本P之前放进去的对象
  3. 因此,有必要对获取到的对象进行某种初始化赋值或者重置操作
func Test_2(t *testing.T) {
	// 多协程获取pool
	pool := sync.Pool{New: func() interface{} {
		fmt.Println("new")
		return "default"
	}}
	// 验证协程内的各个Get获取到的数据并非是独立的
	wg := sync.WaitGroup{}
	for i := 0; i < 2; i++ {
		wg.Add(1)
		i := i
		go func() {
			defer wg.Done()
			val := pool.Get().(string)
			fmt.Printf("init-gorouteine-%d:%s\n", i, val)
			newVal := "goroutine-" + strconv.Itoa(i)
			pool.Put(newVal)
			fmt.Printf("newVal-gorouteine-%d:%s\n", i, newVal)
			for j := 0; j < 20; j++ {
				val = pool.Get().(string)
				fmt.Printf("loop-gorouteine-%d:%s\n", i, val)
				if newVal != val {
					// 不相等,存在错误
					// 并不能期待协程每次取回来的数据都是一致的
					// 有可能从共享池取到其它协程的数据(当自己的空间被释放时)
					t.Errorf("不相等,存在错误gorouteine-%d:%s\n", i, val)
					runtime.Goexit()
				}
				pool.Put(val)
				runtime.Gosched()
			}

		}()
	}

	wg.Wait()
}

pool的意义

提高性能的几个利器,并发预处理缓存。而pool就是缓存。pool减少了申请堆内存分配的次数。降低了程序的GC频繁度。以下对比了使用pool和不使用pool的性能:
go test -bench ".*_3" -run '' .\testpool_test.go -v -benchmem

func Benchmark_3(b *testing.B)  {

	// 对比,分配一个大堆时,采用pool和不采用pool的性能对比
	// 协程数:100,每次需求对象大小1m
	const routineCount = 10
	const size = 1 << 20

	b.Run("no-pool", func(b *testing.B) {

		wg := sync.WaitGroup{}
		for i := 0; i < routineCount; i++ {
			wg.Add(1)
			go func() {
				defer wg.Done()
				for j := 0; j < b.N; j++ {
					// 申请内存
					b := make([]byte, size)
					b[0] = 1
				}
			}()
		}
		wg.Wait()
	})

	b.Run("pool", func(b *testing.B) {
		wg := sync.WaitGroup{}
		pool := sync.Pool{
			New: func() interface{} {
				b := make([]byte, size)
				return b
			},
		}
		for i := 0; i < routineCount; i++ {
			wg.Add(1)
			go func() {
				defer wg.Done()
				for j := 0; j < b.N; j++ {
					// 申请内存
					b := pool.Get().([]byte)
					b[0] = 1
					pool.Put(b)
				}
			}()
		}
		wg.Wait()
	})
}

测试结果,使用pool理论达到了每秒1281万次,而不使用pool理论每秒2551次,差距巨大。
使用pool的内存操作仅256B每次,而不使用pool达到了1m多(每次申请的内存就是1m)。申请的对象数量一致。可以预见到使用pool可以复用对象,而不是反复重新分配堆内存和释放堆内存。在高并发场景下,对需要频繁创建对象时使用pool可以大大提高性能。

goos: windows
goarch: amd64
pkg: mytest/testpool
cpu: AMD Ryzen 9 3900X 12-Core Processor
Benchmark_3
Benchmark_3/no-pool
Benchmark_3/no-pool-24              2551            477658 ns/op        10485801 B/op         10 allocs/op
Benchmark_3/pool
Benchmark_3/pool-24             12814890                86.67 ns/op          256 B/op         10 allocs/op
PASS
ok      mytest/testpool 3.488s

gin 中的pool

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()

	engine.handleHTTPRequest(c)

	engine.pool.Put(c)
}

在gin中使用到了pool用来复用Context对象。在并发场景下,其是线程安全的。gin对Get取回来的对象都进行了reset操作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

go sync.Pool 深入 的相关文章

  • ftp文件传输 vscode_VSCode插件--ftp-sync

    VSCode的ftp sync是一个通过sftp ftp自动同步本地文件到远程机器的插件 xff0c 配置之后 xff0c 远程调试会方便很多 xff0c 本地修改文件保存后会自动同步到远程机器 安装插件后 xff0c shift 43 c
  • Android Studio Gradle project Sync Failed解决方法

    1 查看项目使用的gradle和本地gradle是否一致 本地gradle一般目录在C Users admin gradle文件夹下面 项目使用的gradle在项目的gradle wrapper properties文件中 distribu
  • 正向全局代理(proxy_pool + Proxifier 4.01)

    0x00 准备阶段 Part 1 redis 下载安装好 redis 用于保存 ip 这是 Windows 5 0 14安装版的链接 链接 xff1a https pan baidu com s 1eUTTQ XIeGjWdvXsZ Bpg
  • repo sync遇到warning: project ‘repo‘ branch ‘stable‘ is not signed

    warning project 39 repo 39 branch 39 stable 39 is not signed This can happen on Linux and Mac I 39 ve personally experie
  • 解决eclipse中出现Resource is out of sync with the file system问题

    作者 xff1a reille 本博客网址 xff1a http blog csdn net reille xff0c 转载本博客原创文章请注明出处 本文内容概要 xff1a 解决eclipse中出现Resource is out of s
  • SLAM中姿态估计的图优化方法比较(g2o/Ceres/GTSAM/SE-Sync)

    编辑 深蓝AI 点击下方卡片 xff0c 关注 自动驾驶之心 公众号 ADAS巨卷干货 xff0c 即可获取 后台回复 SLAM综述 获取视觉SLAM 激光SLAM RGBD SLAM等多篇综述 xff01 本文是对论文 A Compari
  • Gradle sync failed: Could not GET gradle-3.0.0-beta4.pom

    在不电脑间移动android工程项目时出现Gradle sync fail Could not resolve com android tools build gradle 3 0 0 beta4 以为是android studio版本的问
  • linux间文件实时同步(syncthing) ---带历史版本“后悔药”

    一 概念简介 syncthing 一款开源免费的数据同步工具 基于P2P的跨平台文件同步工具 通过tcp建立设备连接 再通过TLS进行数据安全传输 支持公网与局域网搭建 支持单双向同步与历史版本控制 后悔药 备份机未感染情况下 历史版本理论
  • Go中sync 包的 Once 使用

    文章目录 背景 One 简介 示例 注意 源码解读 背景 在系统初始化的时候 某些代码只想被执行一次 这时应该怎么做呢 没有学习 Once 前 大家可能想到 声明一个标识 表示是否初始化过 然后初始化这个标识加锁 更新这个标识 但是学会了
  • python进程池,每个进程都有超时,而不是池中的所有进程

    我需要运行许多进程 但不是全部运行 例如同时运行 4 个进程 multiprocessing Pool正是我所需要的 但问题是 如果进程持续超过超时 例如 3 秒 我需要终止该进程 Pool仅支持等待所有进程的超时 而不是每个进程的超时 这
  • 分析 python 多处理池

    我试图在多处理池中的每个进程上运行 cProfile runctx 以了解我的源中的多处理瓶颈 这是我正在尝试做的事情的简化示例 from multiprocessing import Pool import cProfile def sq
  • 在 python 中填充队列并管理多处理

    我在 python 中遇到这个问题 我有一个 URL 队列 需要时不时地检查一下 如果队列已满 我需要处理队列中的每个项目 队列中的每个项目必须由单个进程处理 多处理 到目前为止 我设法 手动 实现这一点 如下所示 while 1 self
  • 我可以在 Pool.imap 调用的函数中使用多处理队列吗?

    我正在使用 python 2 7 并尝试在自己的进程中运行一些 CPU 繁重的任务 我希望能够将消息发送回父进程 以使其了解进程的当前状态 多处理队列似乎对此很完美 但我不知道如何让它工作 所以 这是我的基本工作示例 不使用队列 impor
  • Java字符串实例化

    为什么此代码返回 false 而不是 true package com company public class Main public static void main String args String fullName Name L
  • Python 多处理池 OSError:打开的文件太多

    我得检查一下需要多少时间do something 总共需要一对包含 30k 元素的列表 下面是我的代码 def run a b data p datetime datetime now val do something a b data 0
  • 如何获取 Python 多处理池剩余的“工作”量?

    到目前为止 每当我需要使用multiprocessing http docs python org 2 library multiprocessing htm我通过手动创建 进程池 并与所有子进程共享工作队列来完成此操作 例如 from m
  • Python multiprocessing - 跟踪pool.map操作的过程

    我有一个执行一些模拟的功能 返回字符串格式的数组 我想运行模拟 函数 变化的输入参数值 超过 10000 个可能的输入值 并将结果写入单个文件 我正在使用多处理 特别是 pool map 函数 并行运行模拟 整个过程运行模拟功能超过1000
  • 多处理池内进程超时

    当我使用以下代码时 池结果总是返回超时 我在做的事情在逻辑上是否不正确 from multiprocessing import Pool Process cpu count def add num return num 1 def add
  • 并行处理 - 池 - Python

    我正在尝试学习如何在 Python 中使用多重处理 我读到多重处理 http docs python org 2 library multiprocessing html 我尝试做这样的事情 我有以下类 部分代码 它有一个生成 vorono
  • 如何让 Pool.map 采用 lambda 函数

    我有以下功能 def copy file source file target dir pass 现在我想用multiprocessing立即执行此函数 p Pool 12 p map lambda x copy file x target

随机推荐

  • 【ubuntu20】filezilla连接主机和ubuntu20

    1 普通用户登录 sudo apt update sudo apt install openssh server 安装ssh br sudo systemctl status ssh 确认是否运行 br sudo ufw allow ssh
  • CentOS7+Nginx+阿贝云服务器使用心得

    最近有一个项目需要使用云服务器展示demo xff0c 由于是临时使用就想找一个免费的云服务器 由于以前在阿里云 腾讯云 华为云 百度云 亚马逊云都用过免费版 xff0c 这次就在网上搜了一下其它的免费云 正好就找到了阿贝云 https w
  • WSL2运行sudo gnome-session没反应

    必须注意当前用户 xff0c 不一定是在root下创建的gnome session xff0c 以我为例 xff0c 我当时是在leo用户下安装的gnome session xff0c 但之后一直都是以root用户登录 xff0c 所以运行
  • n个人围成一圈,第一个开始报数(1-3),凡报数3退出。问最后留下的人是原来第几号?

    include lt stdio h gt int main int i 61 0 j 61 0 k 61 0 n x int a 100 printf 34 please input a nu 34 scanf 34 d 34 amp n
  • 使用sea-orm执行migrate

    源码github地址 seaormdemo 一 下载工具链 sea orm cli 是sea orm 提供的工具链 xff0c 可通过cargo下载 cargo span class token function install span
  • PVE安装更新源错误

    pve系统ping 网络不通且不能进行apt install 描述 root 64 xuyuquan span class token comment apt get update span Err 1 http ftp debian or
  • failed to run command ‘java’: No such file or directory

    failed to run command java No such file or directory 程序里远程执行shell命令 xff08 nohup java jar xff09 的执行 xff0c 后台日志报错如下 xff1a
  • vue3中的setup函数

    原文 xff1a vue3中的setup函数 落雪小轩韩的博客 CSDN博客 vue3setup 一 概念 xff1a setup是vue3中的一个新的配置项 xff0c 值为一个函数 xff0c 我们在组件中用到的数据 方法等等 xff0
  • vue同步请求

    原文地址 xff1a vue 同步请求 Aa duidui的博客 CSDN博客 vue同步请求 同步请求执行的顺序 async await 挂上的才是同步 没挂上的还是异步 async 方法名 await 请求方法 参数 then res
  • Anaconda上设置虚拟环境,并在jupyter notebook中切换。

    个人记录 xff0c 但欢迎阅读和赐教 我之前在Anaconda Navigator中建立虚拟环境 xff0c 然后在jupyter notebook的terminal中增加对应环境的ipykernel xff0c 这样可行 xff0c 但
  • 字符,字节和编码

    级别 xff1a 初级 摘要 xff1a 本文介绍了字符与编码的发展过程 xff0c 相关概念的正确理解 举例说明了一些实际应用中 xff0c 编码的实现方法 然后 xff0c 本文讲述了通常对字符与编码的几种误解 xff0c 由于这些误解
  • http协议原理

    HTTP工作原理 HTTP协议定义Web客户端如何从Web服务器请求Web页面 xff0c 以及服务器如何把Web页面传送给客户端 HTTP协议采用了请求 响应模型 客户端向服务器发送一个请求报文 xff0c 请求报文包含请求的方法 URL
  • TLS协议/SSL协议

    历史背景 SSL Secure Socket Layer 安全套接层 是基于HTTPS下的一个协议加密层 xff0c 最初是由网景公司 xff08 Netscape xff09 研发 xff0c 后被IETF xff08 The Inter
  • TCP协议

    TCP 基础 https www jianshu com p ef892323e68f TCP 使用固定的连接 TCP 用于应用程序之间的通信 当应用程序希望通过 TCP 与另一个应用程序通信时 xff0c 它会发送一个通信请求 这个请求必
  • UDP协议

    UDP 概述 xff08 User Datagram Protocol xff0c 用户数据报协议 xff09 用户数据报协议 UDP 只在 IP 的数据报服务之上增加了很少一点的功能 xff0c 这就是复用和分用的功能以及查错检测的功能
  • TCP和UDP的区别

    TCP协议与UDP协议的区别 首先咱们弄清楚 xff0c TCP协议和UDP协议与TCP IP协议的联系 xff0c 很多人犯糊涂了 xff0c 一直都是说TCP协议与UDP协议的区别 xff0c 我觉得这是没有从本质上弄清楚网络通信 xf
  • 网络协议概述

    互联网协议介绍 互联网的核心是一系列协议 xff0c 总称为 互联网协议 xff08 Internet Protocol Suite xff09 xff0c 正是这一些协议规定了电脑如何连接和组网 我们理解了这些协议 xff0c 就理解了互
  • go 编写tcp和udp服务端和客户端

    TCP协议 TCP IP Transmission Control Protocol Internet Protocol 即传输控制协议 网间协议 xff0c 是一种面向连接 xff08 连接导向 xff09 的 可靠的 基于字节流的传输层
  • tcp黏包问题

    服务端代码如下 xff1a span class token keyword package span main span class token keyword import span span class token punctuati
  • go sync.Pool 深入

    new函数的调用时机和pool的内存释放规则 以下代码调用了四次Get函数 xff0c 但是并不是每次都会new 第一次 xff0c 是a 61 pool Get byte xff0c 首次Get xff0c 在pool的private私有