首页 > 其他分享 >Go语言对象池实践

Go语言对象池实践

时间:2024-01-24 17:06:08浏览次数:34  
标签:PooledObject 语言 对象 实践 sync 19 pool Go Pool

对象池是一种在编程中用于优化资源管理的技术。它的基本思想是在应用程序启动时预先创建一组对象,并在需要时重复使用这些对象,而不是频繁地创建和销毁。这种重用的机制有助于减少资源分配和回收的开销,提高程序性能,特别在涉及大量短寿命对象的场景下效果显著。

在Go语言中,对象池通常通过sync.Pool包或自定义数据结构实现。该机制利用Go的垃圾回收策略,通过避免不必要的对象分配来减轻垃圾回收的负担。对象的创建、重用和释放是对象池的核心流程,其中创建发生在对象池为空且需要新对象时,重用则是从对象池中获取现有对象,而释放则是将不再需要的对象放回对象池供其他地方使用。

对象池在高并发和高性能的Go应用中具有广泛应用。例如,在网络编程中,可以使用对象池来维护连接池,避免频繁地创建和关闭连接;在数据库访问中,对象池可以用于管理数据库连接,减少连接的创建和销毁开销。这些实际应用场景充分展示了对象池在提升性能和资源利用率方面的价值。

之前在Java性能测试当中也分享了通用池化框架 Apache common-pool2 以及对应的实践案例,今天分享一下Go语言在对象池实现上的应用。

对象池的优势

这里不得不简单分享一下Go语言的垃圾回收。垃圾回收(Garbage Collection,GC)是一种自动管理内存的机制,用于检测和释放不再使用的内存对象,以防止内存泄漏。Go的垃圾回收机制采用了基于并发的标记-清理算法,以及部分停顿的方式来进行垃圾回收。

Go语言中,频繁创建对象和回收对象会带来两个性能问题。

  1. 频繁分配和销毁对象会造成更多的内存碎片,处理这些碎片会增加额外资源开销。
  2. 频繁分配和销毁对象会导致更频繁的停顿时间。
  3. 频繁分配和销毁对象会带来更多系统资源开销。

为了解决这个问题,处理在优化编码质量和调整GC参数之外,对象池技术是最重要的解决方案。以上三个问题均转化为对象池技术的优点,

在高性能编程实践中,对象池技术是一项不可或缺的战略,它不仅能显著提升系统性能,降低资源开销,还有助于优化内存利用率。通过巧妙地重用已经存在的对象,对象池有效地规避了频繁的对象创建和销毁过程,减轻了系统负担。这对于面临资源稀缺、要求高度响应性的应用环境尤为重要。

在高并发场景下,对象池更是发挥了巨大的作用。并发环境中,多个线程或协程可以从对象池中获取对象,实现了资源的共享与协同,有效提高了程序的并发性能。同时,对象池还有助于避免由于频繁的资源分配导致的内存碎片问题,优化了内存空间的使用,使系统更为稳定。

在一个长时间运行的高性能应用中,对象池的灵活性也是其优势之一。通过动态调整对象池的大小,可以根据实际需求进行优化,确保在不同负载下仍然能够保持高效的性能表现。综合而言,对象池技术的采用在高性能编程中不仅是一项优秀的实践,更是为了应对复杂、高并发应用场景的必备利器。

sync.Pool实现对象池

首先,Go语言自带了 sync.Pool 实现。sync.Pool 是 Go 语言标准库中的一个对象池实现,用于提高对象的重用性,减少对象的创建和垃圾回收的开销。sync.Pool 在并发环境中特别有用,它能够显著提升程序性能。

以下是 sync.Pool 的主要特点和使用方式:

  1. 对象池的创建: 通过 sync.Pool,你可以创建一个对象池,用于存储和管理特定类型的对象。对象池中的对象在被取出后可以被重用,而不是每次都重新创建。
  2. Get 和 Put 操作: 使用 sync.PoolGet 方法可以从对象池中获取一个对象,而 Put 方法则用于将对象放回对象池。这两个操作是并发安全的,可以被多个 goroutine 同时使用。
  3. 对象的生命周期: sync.Pool 并不保证对象会一直存在,对象可能会在任意时刻被垃圾回收。因此,不能假设对象在调用 Get 后一直有效,需要重新初始化。
  4. 适用于短生命周期对象: sync.Pool 特别适用于管理短生命周期的对象,例如临时对象、缓存对象等。对于长时间生存的对象,sync.Pool 的优势可能会减弱。

下面是我用 sync.Pool 创建对象池的演示Demo:

package pool  
  
import (  
    "funtester/ftool"  
    "log"    "sync"    "testing")  
  
// PooledObject  
// @Description: 对象池对象  
type PooledObject struct {  
    Name    string  
    Age     int  
    Address string  
}  
  
// NewObject  
//  
//  @Description: 创建对象  
//  @return *PooledObject  
func NewObject() *PooledObject {  
    log.Println("创建对象")  
    return &PooledObject{  
       Name:    "",  
       Age:     0,  
       Address: "",  
    }  
  
}  
  
// Reset  
//  
//  @Description: 重置对象  
//  @receiver m 对象  
func (m *PooledObject) Reset() {  
    m.Name = ""  
    m.Age = 0  
    m.Address = ""  
    log.Println("重置对象")  
}  
  
type ObjectPool struct {  
    ObjPool sync.Pool  
    Name    string  
}  
  
// NewPool  
//  
//  @Description: 创建对象池  
//  @param size 对象池大小  
//  @return *ObjectPool 对象类型  
func NewPool(size int) *ObjectPool {  
    return &ObjectPool{  
       Name:    "FunTester测试",  
       ObjPool: sync.Pool{New: func() interface{} { return NewObject() }},  
    }  
}  
  
// Get  
//  
//  @Description: 获取对象  
//  @receiver p 对象池  
//  @return *PooledObject 对象  
func (p *ObjectPool) Get() *PooledObject {  
    return p.ObjPool.Get().(*PooledObject)  
}  
  
// Back  
//  
//  @Description: 回收对象  
//  @receiver p 对象池  
//  @param obj 回收的对象  
func (p *ObjectPool) Back(obj *PooledObject) {  
    obj.Reset()  
    p.ObjPool.Put(obj)  
}  
  
func TestPool1(t *testing.T) {  
    pool := NewPool(1)  
    get := pool.Get()  
    get.Name = "FunTester"  
    get.Age = 18  
    get.Address = "地球"  
    log.Printf("%T %s", get, ftool.ToString(get))  
    pool.Back(get)  
    get2 := pool.Get()  
    log.Printf("%T %s", get, ftool.ToString(get2))  
}

控制台打印:

=== RUN   TestPool1
2024/01/19 23:05:17 创建对象
2024/01/19 23:05:17 *pool.PooledObject &{FunTester 18 地球}
2024/01/19 23:05:17 重置对象
2024/01/19 23:05:17 *pool.PooledObject &{ 0 }
--- PASS: TestPool1 (0.00s)
PASS

PS:这里不建议使用并发安全类来控制对象池数量,因为在使用过程中,对象池中的对象可能会被垃圾回收机制销毁,会导致额外的未知问题。但是可以使用并发安全类进行借出和归还的计数,从而实现对最大可借数量的限制,不过略微复杂,并不适用于性能测试中的场景。

chan实现对象池

我们还可以借助 chan 来实现对象池。可以把 chan 用来存储对象,借和还都只是从 chan 中取出和放入对象。这样做的好处如下几点:

  1. 并发安全。由于 chan 操作是原子性的,所以整个的借还过程都是并发安全的。
  2. 数量可控。可以通过设置 chan 的容量控制对象总量。
  3. 阻塞处理。当无足够对象或者过多对象时,可以阻塞以便进行逻辑处理。
  4. 可读性好。使用 chan 实现对象池,代码清晰易读,便于维护。

下面是我的实现Demo:

package pool  
  
import (  
    "log"  
    "reflect"    "testing")
  
type ObjectPool2 struct {  
    objects chan *PooledObject  
    Name    string  
}  
  
// NewPool  
//  
//  @Description: 创建对象池  
//  @param size 对象池大小  
//  @return *ObjectPool 对象类型  
func NewPool2(size int) *ObjectPool2 {  
    return &ObjectPool2{  
       objects: make(chan *PooledObject, size),  
       Name:    "FunTester测试",  
    }  
}  
  
// Get  
//  
//  @Description: 获取对象  
//  @receiver p 对象池  
//  @return *PooledObject 对象  
func (p *ObjectPool2) Get2() *PooledObject {  
    select {  
    case obj := <-p.objects:  
       return obj  
    default:  
       log.Println("额外创建对象")  
       return NewObject()  
    }  
}  
  
// Back  
//  
//  @Description: 回收对象  
//  @receiver p 对象池  
//  @param obj 回收的对象  
func (p *ObjectPool2) Back(obj *PooledObject) {  
    obj.Reset()  
    select {  
    case p.objects <- obj:  
    default:  
       obj = nil  
       log.Println("丢弃对象")  
    }  
}  
  
func TestPool2(t *testing.T) {  
    pool := NewPool2(1)  
    get := pool.Get2()  
    object := pool.Get2()  
    log.Printf("%T", get)  
    log.Println(reflect.TypeOf(get))  
    pool.Back(get)  
    pool.Back(object)  
  
}

控制台输出:

=== RUN   TestPool2
2024/01/19 23:19:42 额外创建对象
2024/01/19 23:19:42 创建对象
2024/01/19 23:19:42 额外创建对象
2024/01/19 23:19:42 创建对象
2024/01/19 23:19:42 *pool.PooledObject
2024/01/19 23:19:42 *pool.PooledObject
2024/01/19 23:19:42 重置对象
2024/01/19 23:19:42 重置对象
2024/01/19 23:19:42 丢弃对象
--- PASS: TestPool2 (0.00s)
PASS

虽然chan实现对象池在某些场景下具有优势,但在其他情况下可能不是最佳选择。在一些性能要求较高的场景中,使用更为专业的对象池库或者手动管理对象池的方式可能更为灵活和高效。

第三方库

在Go语言中,有一些第三方库专门用于实现对象池,它们提供了更复杂、灵活、高效的对象池管理机制。以下是一些常用的第三方库,用于实现对象池:

  1. github.com/fatih/pool
    • GitHub 地址: fatih/pool
    • 该库提供了一个通用的对象池实现,支持对任意对象的池化。它允许你自定义对象的创建、销毁和验证逻辑,非常灵活。
  2. github.com/panjf2000/ants/v2
    • GitHub 地址: panjf2000/ants
    • 该库是一个高性能的 goroutine 池,适用于需要并发执行任务的场景。虽然主要关注 goroutine 池,但也可以用作通用的对象池。
  3. github.com/jolestar/go-commons-pool
    • GitHub 地址: jolestar/go-commons-pool
    • 该库是一个通用的对象池实现,支持池化各种类型的对象。它提供了丰富的配置选项,允许你自定义对象创建、销毁和验证的逻辑。
  4. github.com/avast/retry-go
    • GitHub 地址: avast/retry-go
    • 该库提供了一个灵活的对象池实现,支持对获取和释放对象的重试策略。适用于需要在获取对象时进行重试的情况。

这些库提供了比标准库的 sync.Poolchan实现 更为复杂且灵活,可以根据具体需求进行选择。后面有机会我会选择其中一两种学习实践,然后分享。

标签:PooledObject,语言,对象,实践,sync,19,pool,Go,Pool
From: https://blog.51cto.com/FunTester/9399952

相关文章

  • Ubuntu22.04 上使用 C 语言实现简易聊天室程序
    Linux程序设计课程作业,在此记录下我的实现过程和思路,如有错误或不足,欢迎指正!代码:https://github.com/Tangsmallrong/Linux_network_program/1.需求设计并实现一个简单的聊天室程序,实现如下功能:用户界面:实现基于终端的字符界面,支持用户管理,包括用户名和密码的注册与登录。......
  • C语言中的at+;wt+;rt+;wb+;是什么意思
    这些都是C语言打开文件函数fopen的一个参数打开文件方式的值:定义函数FILE*fopen(constchar*path,constchar*mode);函数说明参数path字符串包含欲打开的文件路径及文件名,参数mode字符串则代表着流形态。mode有下列几种形态字符串:r打开只读文件,该文件必须存在。r+......
  • 四、掌握ArgoCD:安装、Gitlab集成和RBAC配置以实现无缝持续部署
    Gitlab集成    然后点击提交(saveapplication),将得到应用创建后的信息:  注册完成后,我们可以看到应用现在只有0个Clients,即还没有任何实例作为这个Application的实例进行认证:但不管怎么说,应用注册这一步我们算成功完成啦!GiveMeFive~用如下命令编辑con......
  • 22-有参转录组实战8-基因功能注释_GO_KEGG_swissprot_pfam_TFDB_iTAK
    #进行功能注释时,我们只用到蛋白文件,就是上一期提取序列的文件“Ptri.protein.fa”。#使用命令“grep-c">"Ptri.protein.fa”统计下“>”的个数,发现有52400个。#新建文件夹“swissprot”wgethttps://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase......
  • Gateway API 实践之(四)FSM Gateway 的重试功能
    网关的重试功能是一种重要的网络通信机制,旨在提高系统服务调用的可靠性和容错性。这个功能允许网关在初次请求失败时自动重新发送请求,从而减少临时性问题(如网络波动、服务瞬时过载等)对最终用户体验的影响。它的工作原理是,当网关向下游服务发送请求时,如果遇到特定类型的失败(如连接错......
  • 从零开始搭建Go语言开发环境
    从零开始搭建Go语言开发环境–雨林霖(yuyulinlin.cn)    ......
  • go库bytes
    bytesstrings的基本操作与bytes的基本操作基本类似 #基本操作#比较和比较相关的方法有:funcEqual(a,b[]byte)boolfuncEqualFold(s,t[]byte)boolfuncCompare(a,b[]byte)int其中Equal和Compare是使用汇编来实现的。例如:a:=[]byte("hello")b:=[]byte("......
  • go 库I/O
    之前也有博客记录之goio包bufio#Readerbufio.Reader对io.Reader进行了包装,提供了缓冲区功能。定义如下:typeReaderstruct{buf[]byterdio.Reader//readerprovidedbytheclientr,wint//bufreadandwriteposi......
  • 1.27号(本周六)直播:golang开发远程控制工具
    本次的课程的内容为:1、远控编写2、工具代码编写3、工具测试 1月27日晚20:00,我们不见不散~ Ms08067安全实验室专注于网络安全知识的普及和培训,是专业的“图书出版+培训”的网络安全在线教育平台,专注于网络安全领域中高端人才培养。平台已开设Web安全零基础就业,Web高级安全......
  • SQL通用语言--DML(数据操作语言)
    SQL通用语言--DML(数据操作语言)DML-介绍DML英文全称是DataManipulationLanguage,用来对数据库中表的数据记录进行增删改的操作。添加数据(INSERT)修改数据(UPDATE)删除数据(DELETE)DML-添加数据给指定字段添加数据INSERTINTO表名(字段名1,字段名2...)VALUES(......