[每周一更]-(第140期):sync.Pool 使用详解:性能优化的利器

news2025/5/10 2:24:35

在这里插入图片描述

文章目录

      • 一、什么是 `sync.Pool`?
      • 二、`sync.Pool` 的基本作用
      • 三、`sync.Pool` 的主要方法
      • 四、`sync.Pool` 的内部工作原理
      • 五、`sync.Pool` 适用场景
      • 六、使用示例
        • 示例 1:基本使用
        • 输出示例:
        • 示例 2:并发使用
      • 七、一个基于 `sync.Pool` 的 **Benchmark**,对比使用 `sync.Pool` 和直接创建对象的性能差异。
        • Benchmark 测试代码
        • 如何运行 Benchmark?
        • 输出(示例)
        • 结果说明
      • 八、`sync.Pool` 的性能优化
      • 九、注意事项
      • 十、和其他复用方式比较
      • 最后

sync.Pool 是 Go 标准库 sync 包中的一个数据结构,主要用于实现临时对象的池化管理。它的目的是减少频繁的内存分配和垃圾回收,提高性能,尤其在高并发场景下,避免不必要的内存分配和 GC 压力。

在日常 Go 开发中,如果你遇到频繁创建和销毁某些对象的场景,或者你在写一个高并发服务,需要有效控制内存分配和 GC 压力,那么 sync.Pool 就是你值得深入了解的工具。

一、什么是 sync.Pool

sync.Pool 是 Go 标准库 sync 包中的一个对象池结构,主要用于临时对象的复用,避免频繁的内存分配和回收,从而减轻垃圾回收(GC)压力,提高程序性能。

从源码可以看到核心字段如下:

type Pool struct {
    New func() any
    // 其他内部字段不对外暴露
}

通过 New 函数定义如何创建新对象,调用 Get() 取对象,Put() 放回对象。

二、sync.Pool 的基本作用

sync.Pool 允许程序池化临时对象,并在需要时提供这些对象。池中的对象通常是短期使用的对象,它们在使用后可以被重新归还给池中,以便后续复用。这种对象池机制对于避免频繁的对象创建和销毁非常有用,特别是在并发访问大量临时对象的场景中。

三、sync.Pool 的主要方法

  1. Get()
    • 用于从池中获取一个对象。如果池中有对象,Get() 返回一个对象。如果池中没有对象,会调用提供的 New 函数来创建一个新对象(如果定义了 New)。
  2. Put()
    • 用于将一个对象放回池中,供后续复用。需要注意的是,不是所有的对象都适合放回池中,特别是那些有副作用的对象应该避免复用。
  3. New
    • sync.PoolNew 字段是一个函数类型,可以传入一个用来生成新对象的函数。当池中没有对象时,Get() 方法会调用 New 来生成一个对象。如果不需要此功能,则可以设置 Newnil

四、sync.Pool 的内部工作原理

  • sync.Pool 内部实现通常是基于一个链表,它维护了一个池中对象的集合,支持高效的插入和删除。
  • 该池使用了 无锁机制,即使在并发环境下,也能保证对象池的高效访问。
  • 池中的对象在被 Put() 放回池中后,可以在任何时刻被重新获取,除非垃圾回收器清理了池中不再使用的对象。
  • Go 的垃圾回收机制会自动回收池中未使用的对象,因此 sync.Pool 中的对象并不会长时间持有内存,避免了内存泄漏的风险。

五、sync.Pool 适用场景

sync.Pool 主要适用于以下几种场景:

  1. 临时对象复用(临时对象生命周期短,但创建开销大):
    • 在高并发场景中,尤其是需要频繁创建和销毁对象的地方,可以使用对象池来复用临时对象,减少内存分配的开销。
  2. 减少垃圾回收压力(手动管理对象回收较复杂,不适合主动释放内存):
    • 使用 sync.Pool 可以有效减少内存分配和垃圾回收(GC)的压力。因为池中的对象可以被重复利用,而不是频繁地创建和销毁。
  3. 提高性能(高并发服务中频繁创建、销毁对象(如 []byte、结构体等)):
    • 在高并发环境下,使用池化对象可以避免频繁的内存分配和垃圾回收,提高程序的性能。

不适合

  • 对象生命周期较长
  • 需要确定性回收资源(如文件句柄、数据库连接)

六、使用示例

sync.Pool 的变量复用体现:通过 Put() 放回对象,再用 Get() 获取时重用旧对象,避免了重复创建内存结构。

package main

import (
	"fmt"
	"sync"
)

// 假设我们有一个临时对象类型
type MyObject struct {
	ID int
}

func main() {
	// 创建一个 sync.Pool,New函数用来生成一个新的对象
	var pool = &sync.Pool{
		New: func() interface{} {
			// 创建一个新的 MyObject 对象
			return &MyObject{}
		},
	}

	// 从池中获取一个对象
	obj := pool.Get().(*MyObject)
	obj.ID = 42 // 使用对象

	// 打印对象的 ID
	fmt.Println("Object ID:", obj.ID)

	// 将对象放回池中
	pool.Put(obj)

	// 从池中获取另一个对象
	anotherObj := pool.Get().(*MyObject)
	fmt.Println("Another Object ID:", anotherObj.ID) // 此时 ID 会是0,因为是新创建的对象
}

下面以一个简单的对象池示例来演示基本用法:

示例 1:基本使用
package main

import (
    "fmt"
    "sync"
)

type Buffer struct {
    Data []byte
}

var bufferPool = sync.Pool{
    New: func() any {
        fmt.Println("New Buffer created")
        return &Buffer{Data: make([]byte, 0, 1024)}
    },
}

func main() {
    // 从池中获取对象
    buf1 := bufferPool.Get().(*Buffer)
    buf1.Data = append(buf1.Data, []byte("Hello")...)
    fmt.Println("buf1:", string(buf1.Data))

    // 使用完后放回池中
    buf1.Data = buf1.Data[:0] // 重置内容
    bufferPool.Put(buf1)

    // 再次获取,复用之前的对象
    buf2 := bufferPool.Get().(*Buffer)
    fmt.Println("buf2:", string(buf2.Data))
}
输出示例:
New Buffer created
buf1: Hello
buf2:

可见第二次 Get() 没有再次创建新对象,而是复用了上一次的。


示例 2:并发使用
var intSlicePool = sync.Pool{
    New: func() any {
        return make([]int, 0, 100)
    },
}

func worker(wg *sync.WaitGroup, id int) {
    defer wg.Done()

    s := intSlicePool.Get().([]int)
    s = append(s, id)

    fmt.Printf("Worker %d, slice: %v\n", id, s)

    s = s[:0] // 重置
    intSlicePool.Put(s)
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(&wg, i)
    }

    wg.Wait()
}

该例子模拟了并发下的对象复用场景,可以有效减少 slice 分配。

七、一个基于 sync.PoolBenchmark,对比使用 sync.Pool 和直接创建对象的性能差异。

Benchmark 测试代码
package main

import (
    "bytes"
    "sync"
    "testing"
)

type Buffer struct {
    Data *bytes.Buffer
}

var bufferPool = sync.Pool{
    New: func() any {
        return &Buffer{Data: new(bytes.Buffer)}
    },
}

// 不使用 sync.Pool,直接创建
func BenchmarkWithoutPool(b *testing.B) {
    for i := 0; i < b.N; i++ {
        buf := &Buffer{Data: new(bytes.Buffer)}
        buf.Data.WriteString("Hello World")
        _ = buf.Data.String()
    }
}

// 使用 sync.Pool 重复利用
func BenchmarkWithPool(b *testing.B) {
    for i := 0; i < b.N; i++ {
        buf := bufferPool.Get().(*Buffer)
        buf.Data.Reset()
        buf.Data.WriteString("Hello World")
        _ = buf.Data.String()
        bufferPool.Put(buf)
    }
}

如何运行 Benchmark?

创建一个 _test.go 文件,例如 buffer_pool_test.go,然后运行:

go test -bench=. -benchmem

输出(示例)
BenchmarkWithoutPool-10   	  500000	      2400 ns/op	   320 B/op	      4 allocs/op
BenchmarkWithPool-10      	1000000	      1200 ns/op	     0 B/op	      0 allocs/op

结果说明
  • ns/op:每次操作耗时,越低越好
  • B/op:每次操作分配的内存字节数
  • allocs/op:每次操作的内存分配次数

从示例结果可见,使用 sync.Pool 明显减少了内存分配次数和内存开销,同时也提升了执行效率。

八、sync.Pool 的性能优化

  1. 减少对象创建和销毁的开销
    • 在高并发环境下,频繁创建对象会导致内存分配的开销和垃圾回收压力。通过对象池化,可以减少创建和销毁对象的次数,提升性能。
  2. 适合临时对象的复用
    • sync.Pool 更适合存储那些生命周期较短、频繁创建和销毁的对象,避免了过多的内存分配和垃圾回收操作。
  3. 避免对象泄漏
    • 如果池中对象不再被使用(例如被 GC 清理),它们将被自动删除,避免了内存泄漏的问题。

九、注意事项

  1. 避免放入重负载的对象
    • 一些资源密集型的对象,如数据库连接或文件句柄,应该避免放入 sync.Pool,因为它们通常不适合复用,并且会导致不可预见的副作用。
  2. 不应对同一对象进行多次 Put()
    • Put() 应该用于将一个对象放回池中以供后续复用。如果将同一对象多次放入池中,可能会导致不可预料的行为。
  3. GC 清理
    • 在 Go 的垃圾回收机制中,池中的对象有时会被 GC 回收。如果池中的对象不再使用,sync.Pool 会自动清理它们。
  4. 适合临时对象复用
    • 比如用于解码、缓冲处理、临时排序等。
  5. 不要指望 Pool 实现跨协程稳定复用
    • sync.Pool 更像是对当前 goroutine 或 CPU 本地缓存的一种优化,跨核心复用的能力有限。

十、和其他复用方式比较

方法优点缺点
sync.Pool简单、线程安全、自动 GC 清理控制不精确、不可预测回收
自定义对象池(channel)更可控,可限制最大数量实现复杂、需要额外锁
手动复用(重用结构体)内存利用最大化需要明确回收点,编码难度高

最后

sync.Pool 是一个非常有用的工具,特别适用于高并发场景中对象的复用,减少内存分配和垃圾回收的开销。它通过对象池化机制,使得临时对象能够被高效地复用,进而提高程序的性能。在使用时,应避免将那些不适合复用或者资源密集型的对象放入池中。

临时对象复用优先用 sync.Pool,长生命周期或资源敏感场景慎用。

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

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

相关文章

3.QT-信号和槽|自定义槽函数|自定义信号}自定义的语法}带参数的信号和槽(C++)

信号和槽 Linux信号 Signal 系统内部的通知机制. 进程间通信的方式. 信号源&#xff1a;谁发的信号.信号的类型&#xff1a;哪种类别的信号信号的处理方式&#xff1a;注册信号处理函数&#xff0c;在信号被触发的时候自动调用执行. Qt中的信号和Linux中的信号&#xff0c;虽…

健康养生之道

在快节奏的现代生活中&#xff0c;健康养生不再是中老年人的专属话题&#xff0c;越来越多的人开始意识到&#xff0c;合理的养生方式是保持良好身体状态和生活质量的关键。​ 饮食养生是健康的基石。遵循 “食物多样、谷类为主” 的原则&#xff0c;保证每天摄入足够的蔬菜、…

Spark-SQL核心编程3

数据加载与保存 通用方式&#xff1a; SparkSQL 提供了通用的保存数据和数据加载的方式。这里的通用指的是使用相同的API&#xff0c;根据不同的参数读取和保存不同格式的数据&#xff0c;SparkSQL 默认读取和保存的文件格式为parquet 数据加载方法&#xff1a; spark.read.lo…

TVM计算图分割--Collage

1 背景 为满足高效部署的需要&#xff0c;整合大量优化的tensor代数库和运行时做为后端成为必要之举。现在的深度学习后端可以分为两类&#xff1a;1&#xff09;算子库(operator kernel libraries)&#xff0c;为每个DL算子单独提供高效地低阶kernel实现。这些库一般也支持算…

MCGS昆仑通太屏笔记

4.3寸&#xff1a;4013ef/e1 7寸&#xff1a;7032kw 特点&#xff1a; 如果是使用组态屏进行调试使用&#xff0c;选择com1如果是实际项目使用&#xff0c;选择com2 操作步骤&#xff1a; 先创建设备窗口&#xff0c;再创建用户界面 在设备窗口界面&#xff0c;依次设置如下…

服务治理-搭建Nacos注册中心

运行nacos.sql文件。 将准备好的nacos目录和nacos.tar包上传。 192.168.59.101是我的虚拟机ip&#xff0c;8848是我们设置的访问端口号。

网络--socket编程(2)

Socket 编程 TCP TCP 网络程序 和刚才 UDP 类似 . 实现一个简单的英译汉的功能 TCP socket API 详解 下面介绍程序中用到的 socket API, 这些函数都在 sys/socket.h 中。 socket(): • socket() 打开一个网络通讯端口 , 如果成功的话 , 就像 open() 一样返回一个…

结合建筑业务讲述TOGAF标准处理哪种架构

TOGAF标准处理哪种架构 内容介绍业务架构业务策略&#xff0c;治理&#xff0c;组织和关键业务流程数据架构组织的逻辑和物理数据资产以及数据管理资源的结构应用架构待部署的各个应用程序&#xff0c;它们之间的交互以及与组织核心业务流程的关系的蓝图技术架构支持业务&#…

C++入门小馆: 深入string类(一)

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…

NHANES指标推荐:WWI

文章题目&#xff1a;Weight-adjusted waist circumference index with hepatic steatosis and fibrosis in adult females: a cross-sectional, nationally representative study (NHANES 2017-2020) DOI&#xff1a;10.1186/s12876-025-03706-4 中文标题&#xff1a;体重调整…

2025.04.18|【Map】地图绘图技巧全解

Add circles Add circles on a Leaflet map Change tile Several background tiles are offered by leaflet. Learn how to load them, and check the possibilities. 文章目录 Add circlesChange tile 2025.04.18【Map】| 地图绘图技巧全解1. 准备工作2. 地理区域着色图&…

PR第一课

目录 1.新建 2.PR内部设置 3.导入素材 4.关于素材窗口 5.关于编辑窗口 6.序列的创建 7.视频、图片、音乐 7.1 带有透明通道的素材 8.导出作品 8.1 打开方法 8.2 导出时&#xff0c;需要修改的参数 1.新建 2.PR内部设置 随意点开 编辑->首选项 中的任意内容&a…

Vue+Notification 自定义消息通知组件 支持数据分页 实时更新

效果图&#xff1a; message.vue 消息组件 子组件 <template><div class"custom-notification"><div class"content"><span click"gotoMessageList(currentMessage.split()[1])">{{ currentMessage.split()[0] }}</…

不规则曲面上两点距离求取

背景 在CT中求皮肤上两点间的弧长。由于人体表面并不是规则的曲面&#xff0c;不可能用圆的弧长求取方法来计算出两点间的弧长。 而在不规则的曲面上求两点的距离&#xff0c;都可以用类似测地线距离求取的方式来求取&#xff08;积分&#xff09;&#xff0c;而转化为搜索路…

性能比拼: Elixir vs Go

本内容是对知名性能评测博主 Anton Putra Elixir vs Go (Golang) Performance (Latency - Throughput - Saturation - Availability) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 对比 Elixir 和 Go 简介 许多人长期以来一直要求我对比 Elixir 和 Go。在本视频…

【Linux网络与网络编程】11.数据链路层mac帧协议ARP协议

前面在介绍网络层时我们提出来过一个问题&#xff1a;主机是怎么把数据交给路由器的&#xff1f;那里我们说这是由数据链路层来做的。 网络上的报文在物理结构上是以mac帧的形式流动的&#xff0c;但在逻辑上是以IP流动的&#xff0c;IP的流动是需要mac帧支持的。 数据链路层解…

lottie深入玩法

A、json文件和图片资源分开 delete 是json资源名字 /res/lottie/delete_anim_images是图片资源文件夹路径 JSON 中引用的图片名&#xff0c;必须与实际图片文件名一致 B、json文件和图片资源分开&#xff0c;并且图片加载不固定 比如我有7张图片&#xff0c;分别命名1~7&…

热门与冷门并存,25西电—电子工程学院(考研录取情况)

1、电子工程学院各个方向 2、电子工程学院近三年复试分数线对比 学长、学姐分析 由表可看出&#xff1a; 1、电子科学与技术25年相较于24年上升20分 2、信息与通信工程、控制科学与工程、新一代电子信息技术&#xff08;专硕&#xff09;25年相较于24年下降25分 3、25vs24推…

Warcraft Logs [Classic] [WCL] BOSS ID query

Warcraft Logs [Classic] [WCL] BOSS ID query 所有副本BOSSID查询 https://wowpedia.fandom.com/wiki/DungeonEncounterID#Retail IDNameMapInstanceIDPatch227High Interrogator GerstahnBlackrock Depths230228Lord RoccorBlackrock Depths230229Houndmaster GrebmarBlackro…

架构师面试(三十一):IM 消息收发逻辑

问题 今天聊一下 IM 系统最核心的业务逻辑。 在上一篇短文《架构师面试&#xff08;三十&#xff09;&#xff1a;IM 分层架构》中详细分析过&#xff0c;IM 水平分层架构包括&#xff1a;【入口网关层】、【业务逻辑层】、【路由层】和【数据访问层】&#xff1b;除此之外&a…