go sync.pool 学习笔记

news/2025/11/9 18:40:11/文章来源:https://www.cnblogs.com/xingzheanan/p/19204669

概述

sync.pool 对象池可以用来复用临时对象,减少内存压力,降低 GC 压力。

示例

基本用法

type Worker struct{}  func (w *Worker) Name() string {  return "worker"  
}  func main() {  workerPool := sync.Pool{New: func() interface{} {  return Worker{}  }}  worker := workerPool.Get().(Worker)  defer workerPool.Put(worker)  name := worker.Name()  fmt.Println(name)  
}

sync.pool 是单对象池,不是多对象池。基本使用方法是 GetPut 方法,Get 用来从对象池中取对象,Put 用来将不用的对象放回对象池中。

适用场景

sync.pool 中的对象可能会被运行时回收。有可能在需要使用时对象被回收而重新创建。因此,sync.pool 适合存储高频创建,作用时间短的对象。比如以下场景:

  • JSON 处理:频繁分配的 []byte 切片;
  • Web 服务:HTTP 请求处理的缓冲区;
  • 数据库操作:连接池的辅助工具;

Go sync.Pool 的陷阱与正确用法:从踩坑到最佳实践 这篇文章写的很好关于 sync.pool 的陷阱和正确用法,可以参考学习,这里就不赘述了。

性能测试

var globalBuf []byte  func BenchmarkAllocateWithoutPool(b *testing.B) {  for i := 0; i < b.N; i++ {  buf := make([]byte, 1024)  globalBuf = buf     // 这里将 buf 赋值给 globalBuf,不然会内存逃逸}  
}  func BenchmarkAllocateWithPool(b *testing.B) {  pool := sync.Pool{New: func() interface{} { return make([]byte, 1024) }}  b.ResetTimer()  for i := 0; i < b.N; i++ {  buf := pool.Get().([]byte)  pool.Put(buf)  }  
}

测试结果:

 go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: go-by-example/sync/pool
cpu: Apple M3
BenchmarkAllocateWithoutPool-8           8672882               137.2 ns/op          1024 B/op          1 allocs/op
BenchmarkAllocateWithPool-8             36728509                31.91 ns/op           24 B/op          1 allocs/op
PASS
ok      go-by-example/sync/pool 3.047s

Go Benchmark的输出格式为:

BenchmarkName-GOMAXPROCS Iterations TimePerOp(ns/op) BytesPerOp(B/op) AllocsPerOp(allocs/op) 
  • TimePerOp:单次操作耗时(纳秒),越小越快。
  • AllocsPerOp:单次操作的内存分配次数,越小对GC越友好。
  • BytesPerOp:单次操作分配的总字节数,越小内存效率越高。

可以看出,使用 sync.pool 对象池相比于不使用 sync.pool 的性能对比:

  • 单次操作耗时占比:31.91 / 137.2 = 23.2%
  • 单次操作分配内存:24/1024 = 2.3%

并发

我们进一步看并发场景下对象复用是什么情况。

非并发场景

首先看非并发场景对象复用情况。示例如下:

type Worker struct{}  func (w *Worker) Name() string {  return "worker"  
}func main() {  runtime.GOMAXPROCS(4)var createWorkerTime int32  workerPool := sync.Pool{New: func() interface{} {  atomic.AddInt32(&createWorkerTime, 1) return Worker{}  }}currencyCount := 1024 * 1for i := 0; i < currencyCount; i++ {  worker := workerPool.Get().(Worker)  time.Sleep(time.Millisecond * 1)  workerPool.Put(worker)  }  fmt.Println("create worker time: ", atomic.LoadInt32(&createWorkerTime))  
}

输出:

create worker time:  1

这里对象只创建了一次。

需要注意的是,sync.poolGetPut 是并发安全的。但是创建对象并不是并发安全的,需要用户自己实现。因此,在 sync.pool.New 中使用 atomic.AddInt32 原子操作并发安全的更新 createWorkerTime 变量。

并发场景

示例如下:

func main() {  runtime.GOMAXPROCS(4)  var createWorkerTime int32  workerPool := sync.Pool{New: func() interface{} {  atomic.AddInt32(&createWorkerTime, 1)  return Worker{}  }}  currencyCount := 1024 * 1  var wg sync.WaitGroup  for i := 0; i < currencyCount; i++ {  wg.Add(1)  go func(i int) {  defer wg.Done()  worker := workerPool.Get().(Worker)  defer workerPool.Put(worker)}(i)  }  wg.Wait()  fmt.Println("create worker time: ", atomic.LoadInt32(&createWorkerTime))
}

输出:

~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  4
~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  4
~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  4

我们仅调整 runtime.GOMAXPROCS 为 8,运行程序输出:

~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  5
~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  6
~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  7

当调整 runtime.GOMAXPROCS 时,对象创建次数不固定。要解释其中发什么了什么,需要从 GPM 入手,runtime.GOMAXPROCS 设置 P 的个数,P 会调度 G 到线程 M 上运行,而对象是 P 私有的,如果 G 上的 P 没有对象,则会创建对象。这也解释了,为什么 P 变多了会影响对象的复用次数。

继续构造示例如下:

currencyCount := 1024 * 1  
var wg sync.WaitGroup  
for i := 0; i < currencyCount; i++ {  wg.Add(1)  go func(i int) {  defer wg.Done()  worker := workerPool.Get().(Worker)  defer workerPool.Put(worker)  time.Sleep(time.Millisecond * 100)  }(i)  
}

我们在协程内加了 time.Sleep(time.Millisecond * 100) 运行三次程序:

~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  1024
~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  1024
~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  1024

更新 runtime.GOMAXPROCS 在此运行三次程序,结果都是 1024

这是为什么呢?

还是和 GPM 有关,对象是 P 私有的,P 调度 G 到协程 M 上运行,如果 P 有对象,则会将对象给 G,将私有的对象置为 nil,下次分配对象时如果没有对象,则会调用 sync.pool.New 创建对象。

这里 G 拿到 P 的私有对象后,在线程 M 上运行。由于设置了 time.Sleep G 陷入阻塞状态,M 会运行下一个 G,下一个 G 发现 P 的私有对象已经被阻塞的 G 拿掉了,又会调用 sync.pool.New 创建对象。如此重复,导致每次对象都在创建。

基于这样的逻辑,我们在构造示例如下:

currencyCount := 1024 * 1  
var wg sync.WaitGroup  
for i := 0; i < currencyCount; i++ {  wg.Add(1)  go func(i int) {  defer wg.Done()  worker := workerPool.Get().(Worker)  defer workerPool.Put(worker)  name := worker.Name()  fmt.Println("worker name: ", name, "currency id: ", i)  }(i)  
}

输出:

~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  980
~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  930
~/project/go-by-example/sync/pool git:[main] go run main.go
create worker time:  884

这里没有用 time.Sleep 使 G 陷入阻塞,而是打印对象的名字。输出的对象创建次数并不固定。

这是因为在有些 P 上, 当前 G 执行完将对象 Put 归还给 P 了,下一个 G 会从 P 上拿到对象。而有些 P,当前 G 并未将对象归还给 P,而下一个 G 又找 P 要对象,触发创建对象逻辑,导致每次运行创建对象的次数都不一样。

小结

本文介绍了 sync.pool 的使用,性能分析及并发场景下的对象复用情况,对于 sync.pool 的原理级了解还是要从源码层面入手。

参考资料

  • Go sync.Pool 的陷阱与正确用法:从踩坑到最佳实践
  • Go sync.Pool

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

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

相关文章

初识分布式训练

假设有N块GPU,模型有ψ个参数。 前提知识:每个参数对应一个梯度值,且SGD每个参数对应一个一阶动量,Adam每个参数对应一个一阶、一个二阶动量DP(data parallel) ​ 数据并行(单进程,多线程,只用一个cpu核),每…

电脑监控软件,后台监控,适合家庭电脑、员工电脑监控

电脑监控软件,后台监控,适合家庭电脑、员工电脑监控 支持异地远程访问,终身授权,可月付年付 不限设备数量,支持win7、10、11 1、电脑位置显示 2、桌面远程观看 3、桌面文件下载 4、完整记录按键输入 5、智能屏幕快…

题解:P10856 【MX-X2-T5】「Cfz Round 4」Xor-Forces

题解 首先,我们先考虑简单的情况,没有修改操作。 由题意得,一段区间颜色段个数可以转化为区间长度减去相邻同色个数,即区间 \([l,r]\) 的颜色段数为 \(r-l+1- \sum^r_{i=l+1}[a_i=a_{i-1}]\)。 考虑线段树,那么一…

python: Virtualenv的安装与应用

一,安装Virtualenv 以ubuntu为例: 安装pip # apt install python3-pip 安装virtualenv # apt install python3-virtualenv二,创建环境 $ virtualenv -p /usr/bin/python3.12 myenv 三,进入/退出 环境 进入 $ sourc…

题解:AT_abc147_f [ABC147F] Sum Difference

题意 在一个等差数列中取出若干个元素,求取出的元素与未取出的元素的差值有多少种可能。 思路 首先,我们有一个式子: \[w(i)=\sum_{i \in S}A_i-\sum_{i \notin S}A_i \]不难看出,该式可以变为: \[w(i)=2\times \…

20231326《密码系统设计》第八周预习报告

20231326《密码系统设计》第八周预习报告 目录20231326《密码系统设计》第八周预习报告学习内容《Windows C/C++加密解密实战》第 12 章1. 什么是 SSL 协议 (Secure Sockets Layer)2. SSL/TLS 协议发展历程3. SSL v3/T…

PERL Docker 容器化部署指南

PERL(Practical Extraction and Reporting Language)是一种高级、通用、解释型、动态编程语言,其语法借鉴了C、Shell脚本、AWK和sed等多种语言特性,广泛应用于系统管理、Web开发、网络编程等领域。通过Docker容器化…

解放双手!使用Roslyn生成代码让你的 HTTP 客户端开发变得如此简单

在现代 .NET 开发中,源代码生成器(Source Generators)是一项强大的功能,它允许开发者在编译时自动生成代码,从而减少样板代码的编写,提高开发效率和代码质量。本文主要介绍使用Roslyn实现两个代码生成器:HttpCl…

pandoc用法

要将你的 `文件V5.0_2.docx` 文件转换为 Markdown 格式,使用 **Pandoc** 是一个非常直接的选择。我已经为你准备好了相应的命令,并补充了一些实用技巧。 ### 📝 核心转换命令 在终端或命令行中,导航到你的 `.docx…

JMeter:性能测试利器全解析 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

251109

JT-JY13T1S1Hello, Tourist information center, Mike speaking, how can i help you? Hi, i want to find out about cockery classes. I believe there is some one-day calsses for tourists. Well, they are open…

electron-vite为linux打包成功,但是安装后运行无反应

属于shark插件没有运行 在代码中使用动态导入,如果导入成功就使用shark压缩,如果没有就不压缩。 `// 动态导入 sharp,如果加载失败则优雅降级 let sharp: any = null let sharpAvailable = false async function lo…

吐血推荐!6款超好用的AI论文写作工具

吐血推荐!6款超好用的AI论文写作工具 在学术写作这条漫长的道路上,高效的工具常常能够助力前行。本文会为你详细介绍6款备受赞誉的AI论文写作工具,让你在论文创作时如虎添翼。 1. 鲲鹏智写:一站式论文深度解决方案…

完整教程:金蝶云星瀚 | 生产制造成本核算终极实操手册(从0到1,含两套完整案例)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

PHP中各种超全局变量使用

在PHP中,超全局变量是由PHP系统预先定义的一套变量,它们在脚本的所有作用域中都是可用的。这意味着开发者不需要执行特定的global语句就能访问这些变量。以下是一些常用的PHP超全局变量以及它们的使用方法和注意事项…

详细介绍:对于返回倒数第 k 个节点、链表的回文结构、链表相交题目的解析

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

实用指南:TensorFlow2 Python深度学习 - TensorFlow2框架入门 - 自动微分和梯度

实用指南:TensorFlow2 Python深度学习 - TensorFlow2框架入门 - 自动微分和梯度pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-…

浏览器Blockstack.org全名字段输入限制缺失漏洞分析

本文详细分析了Blockstack浏览器端全名字段缺乏输入长度限制导致的服务中断风险,包括漏洞发现过程、安全影响评估以及开发团队与安全研究员的讨论过程。漏洞报告 #304073 - browser.blockstack.org 全名字段字符串大小…

2025年维修厂家推荐排行榜单:行业权威解析

摘要 随着制冷行业在2025年的快速发展,维修厂家在保障设备高效运行中扮演关键角色。本文基于行业数据和用户口碑,解析2025年维修厂家排行榜单,重点推荐优质服务商,并提供详细表单供参考,帮助用户选择可靠的合作伙…