iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Go语言中的sync.Pool怎么使用
  • 175
分享到

Go语言中的sync.Pool怎么使用

2023-07-05 23:07:14 175人浏览 安东尼
摘要

本篇内容介绍了“Go语言中的sync.Pool怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. 简介本文将介绍 Go 语言中的 s

本篇内容介绍了“Go语言中的sync.Pool怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    1. 简介

    本文将介绍 Go 语言中的 sync.Pool并发原语,包括sync.Pool的基本使用方法、使用注意事项等的内容。能够更好得使用sync.Pool来减少对象的重复创建,最大限度实现对象的重复使用,减少程序GC的压力,以及提升程序的性能。

    2. 问题引入

    2.1 问题描述

    这里我们实现一个简单的JSON序列化器,能够实现将一个map[string]int序列化为一个jsON字符串,实现如下:

    func IntToStringMap(m map[string]int) (string, error) {   // 定义一个bytes.Buffer,用于缓存数据   var buf bytes.Buffer   buf.Write([]byte("{"))   for k, v := range m {      buf.WriteString(fmt.Sprintf(`"%s":%d,`, k, v))   }   if len(m) > 0 {      buf.Truncate(buf.Len() - 1) // 去掉最后一个逗号   }   buf.Write([]byte("}"))   return buf.String(), nil}

    这里使用bytes.Buffer 来缓存数据,然后按照key:value的形式,将数据生成一个字符串,然后返回,实现是比较简单的。

    每次调用IntToStringMap方法时,都会创建一个bytes.Buffer来缓存中间结果,而bytes.Buffer其实是可以被重用的,因为序列化规则和其并没有太大的关系,其只是作为一个缓存区来使用而已。

    但是当前的实现为每次调用IntToStringMap时,都会创建一个bytes.Buffer,如果在一个应用中,请求并发量非常高时,频繁创建和销毁bytes.Buffer将会带来较大的性能开销,会导致对象的频繁分配和垃圾回收,增加了内存使用量和垃圾回收的压力。

    那有什么方法能够让bytes.Buffer能够最大程度得被重复利用呢,避免重复的创建和回收呢?

    2.2 解决方案

    其实我们可以发现,为了让bytes.Buffer能够被重复利用,避免重复的创建和回收,我们此时只需要将bytes.Buffer缓存起来,在需要时,将其从缓存中取出;当用完后,便又将其放回到缓存池当中。这样子,便不需要每次调用IntToStringMap方法时,就创建一个bytes.Buffer

    这里我们可以自己实现一个缓存池,当需要对象时,可以从缓存池中获取,当不需要对象时,可以将对象放回缓存池中。IntToStringMap方法需要bytes.Buffer时,便从该缓存池中取,当用完后,便重新放回缓存池中,等待下一次的获取。下面是一个使用切片实现的一个bytes.Buffer缓存池。

    type BytesBufferPool struct {   mu   sync.Mutex   pool []*bytes.Buffer}func (p *BytesBufferPool) Get() *bytes.Buffer {   p.mu.Lock()   defer p.mu.Unlock()   n := len(p.pool)   if n == 0 {      // 当缓存池中没有对象时,创建一个bytes.Buffer      return &bytes.Buffer{}   }   // 有对象时,取出切片最后一个元素返回   v := p.pool[n-1]   p.pool[n-1] = nil   p.pool = p.pool[:n-1]   return v}func (p *BytesBufferPool) Put(buffer *bytes.Buffer) {   if buffer == nil {      return   }   // 将bytes.Buffer放入到切片当中   p.mu.Lock()   defer p.mu.Unlock()   obj.Reset()   p.pool = append(p.pool, buffer)}

    上面BytesBufferPool实现了一个bytes.Buffer的缓存池,其中Get方法用于从缓存池中取对象,如果没有对象,就创建一个新的对象返回;Put方法用于将对象重新放入BytesBufferPool当中,下面使用BytesBufferPool优化IntToStringMap

    // 首先定义一个BytesBufferPoolvar buffers BytesBufferPoolfunc IntToStringMap(m map[string]int) (string, error) {   // bytes.Buffer不再自己创建,而是从BytesBufferPool中取出   buf := buffers.Get()   // 函数结束后,将bytes.Buffer重新放回缓存池当中   defer buffers.Put(buf)   buf.Write([]byte("{"))   for k, v := range m {      buf.WriteString(fmt.Sprintf(`"%s":%d,`, k, v))   }   if len(m) > 0 {      buf.Truncate(buf.Len() - 1) // 去掉最后一个逗号   }   buf.Write([]byte("}"))   return buf.String(), nil}

    到这里我们通过自己实现了一个缓存池,成功对InitToStringMap函数进行了优化,减少了bytes.Buffer对象频繁的创建和回收,在一定程度上提高了对象的频繁创建和回收。

    但是,BytesBufferPool这个缓存池的实现,其实存在几点问题,其一,只能用于缓存bytes.Buffer对象;其二,不能根据系统的实际情况,动态调整对象池中缓存对象的数量。假如某段时间并发量较高,bytes.Buffer对象被大量创建,用完后,重新放回BytesBufferPool之后,将永远不会被回收,这有可能导致内存浪费,严重一点,也会导致内存泄漏。

    既然自定义缓存池存在这些问题,那我们不禁要问,Go语言标准库中有没有提供了更方便的方式,来帮助我们缓存对象呢?

    别说,还真有,Go标准库提供了sync.Pool,可以用来缓存那些需要频繁创建和销毁的对象,而且它支持缓存任何类型的对象,同时sync.Pool是可以根据系统的实际情况来调整缓存池中对象的数量,如果一个对象长时间未被使用,此时将会被回收掉。

    相对于自己实现的缓冲池,sync.Pool的性能更高,充分利用多核cpu的能力,同时也能够根据系统当前使用对象的负载,来动态调整缓冲池中对象的数量,而且使用起来也比较简单,可以说是实现无状态对象缓存池的不二之选。

    下面我们来看看sync.Pool的基本使用方式,然后将其应用到IntToStringMap方法的实现当中。

    3. 基本使用

    3.1 使用方式

    3.1.1 sync.Pool的基本定义

    sync.Pool的定义如下: 提供了Get,Put两个方法:

    type Pool struct {  noCopy noCopy  local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal  localSize uintptr        // size of the local array  victim     unsafe.Pointer // local from previous cycle  victimSize uintptr        // size of victims array  New func() any}func (p *Pool) Put(x any) {}func (p *Pool) Get() any {}
    • Get方法: 从sync.Pool中取出缓存对象

    • Put方法: 将缓存对象放入到sync.Pool当中

    • New函数: 在创建sync.Pool时,需要传入一个New函数,当Get方法获取不到对象时,此时将会调用New函数创建新的对象返回。

    3.1.2 使用方式

    当使用sync.Pool时,通常需要以下几个步骤:

    • 首先使用sync.Pool定义一个对象缓冲池

    • 在需要使用到对象时,从缓冲池中取出

    • 当使用完之后,重新将对象放回缓冲池中

    下面是一个简单的代码的示例,展示了使用sync.Pool大概的代码结构:

    type struct data{    // 定义一些属性}//1. 创建一个data对象的缓存池var dataPool = sync.Pool{New: func() interface{} {   return &data{}}}func Operation_A(){    // 2. 需要用到data对象的地方,从缓存池中取出    d := dataPool.Get().(*data)    // 执行后续操作    // 3. 将对象重新放入缓存池中    dataPool.Put(d)}

    3.2 使用例子    

    下面我们使用sync.Pool来对IntToStringMap进行改造,实现对bytes.Buffer对象的重用,同时也能够自动根据系统当前的状况,自动调整缓冲池中对象的数量。

    // 1. 定义一个bytes.Buffer的对象缓冲池var buffers sync.Pool = sync.Pool{   New: func() interface{} {      return &bytes.Buffer{}   },}func IntToStringMap(m map[string]int) (string, error) {   // 2. 在需要的时候,从缓冲池中取出一个bytes.Buffer对象   buf := buffers.Get().(*bytes.Buffer)   buf.Reset()   // 3. 用完之后,将其重新放入缓冲池中   defer buffers.Put(buf)   buf.Write([]byte("{"))   for k, v := range m {      buf.WriteString(fmt.Sprintf(`"%s":%d,`, k, v))   }   if len(m) > 0 {      buf.Truncate(buf.Len() - 1) // 去掉最后一个逗号   }   buf.Write([]byte("}"))   return buf.String(), nil}

    上面我们使用sync.Pool实现了一个bytes.Buffer的缓冲池,在 IntToStringMap 函数中,我们从 buffers 中获取一个 bytes.Buffer 对象,并在函数结束时将其放回池中,避免了频繁创建和销毁 bytes.Buffer 对象的开销。

    同时,由于sync.PoolIntToStringMap调用不频繁的情况下,能够自动回收sync.Pool中的bytes.Buffer对象,无需用户操心,也能减小内存的压力。而且其底层实现也有考虑到多核cpu并发执行,每一个processor都会有其对应的本地缓存,在一定程度也减少了多线程的开销。

    从上面可以看出,sync.Pool使用起来非常简单,但是其还是存在一些注意事项,如果使用不当的话,还是有可能会导致内存泄漏等问题的,下面就来介绍sync.Pool使用时的注意事项。

    4.使用注意事项

    4.1 需要注意放入对象的大小

    如果不注意放入sync.Pool缓冲池中对象的大小,可能出现sync.Pool中只存在几个对象,却占据了大量的内存,导致内存泄漏。

    这里对于有固定大小的对象,并不需要太过注意放入sync.Pool中对象的大小,这种场景出现内存泄漏的可能性小之又小。但是,如果放入sync.Pool中的对象存在自动扩容的机制,如果不注意放入sync.Pool中对象的大小,此时将很有可能导致内存泄漏。下面来看一个例子:

    func Sprintf(fORMat string, a ...any) string {   p := newPrinter()   p.doPrintf(format, a)   s := string(p.buf)   p.free()   return s}

    Sprintf方法根据传入的format和对应的参数,完成组装,返回对应的字符串结果。按照普通的思路,此时只需要申请一个byte数组,然后根据一定规则,将format参数的内容放入byte数组中,最终将byte数组转换为字符串返回即可。

    按照上面这个思路我们发现,其实每次使用到的byte数组是可复用的,并不需要重复构建。

    实际上Sprintf方法的实现也是如此,byte数组其实并非每次创建一个新的,而是会对其进行复用。其实现了一个pp结构体,format参数按照一定规则组装成字符串的职责,交付给pp结构体,同时byte数组作为pp结构体的成员变量。

    然后将pp的实例放入sync.Pool当中,实现pp重复使用目的,从而简介避免了重复创建byte数组导致频繁的GC,同时也提升了性能。下面是newPrinter方法的逻辑,获取pp结构体,都是从sync.Pool中获取:

    var ppFree = sync.Pool{   New: func() any { return new(pp) },}// newPrinter allocates a new pp struct or grabs a cached one.func newPrinter() *pp {    // 从ppFree中获取pp   p := ppFree.Get().(*pp)   // 执行一些初始化逻辑   p.panicking = false   p.erroring = false   p.wrapErrs = false   p.fmt.init(&p.buf)   return p}

    下面回到上面的byte数组,此时其作为pp结构体的一个成员变量,用于字符串格式化的中间结果,定义如下:

    // Use simple []byte instead of bytes.Buffer to avoid large dependency.type buffer []bytetype pp struct {   buf buffer   // 省略掉其他不相关的字段}

    这里看起来似乎没啥问题,但是其实是有可能存在内存浪费甚至内存泄漏的问题。假如此时存在一个非常长的字符串需要格式化,此时调用Sprintf来实现格式化,此时pp结构体中的buffer也同样需要不断扩容,直到能够存储整个字符串的长度为止,此时pp结构体中的buffer将会占据比较大的内存。

    Sprintf方法完成之后,重新将pp结构体放入sync.Pool当中,此时pp结构体中的buffer占据的内存将不会被释放。

    但是,如果下次调用Sprintf方法来格式化的字符串,长度并没有那么长,但是此时从sync.Pool中取出的pp结构体中的byte数组长度却是上次扩容之后的byte数组,此时将会导致内存浪费,严重点甚至可能导致内存泄漏。

    因此,因为pp对象中buffer字段占据的内存是会自动扩容的,对象的大小是不固定的,因此将pp对象重新放入sync.Pool中时,需要注意放入对象的大小,如果太大,可能会导致内存泄漏或者内存浪费的情况,此时可以直接抛弃,不重新放入sync.Pool当中。事实上,pp结构体重新放入sync.Pool也是基于该逻辑,其会先判断pp结构体中buffer字段占据的内存大小,如果太大,此时将不会重新放入sync.Pool当中,而是直接丢弃,具体如下:

    func (p *pp) free() {   // 如果byte数组的大小超过一定限度,此时将会直接返回   if cap(p.buf) > 64<<10 {      return   }   p.buf = p.buf[:0]   p.arg = nil   p.value = reflect.Value{}   p.wrappedErr = nil      // 否则,则重新放回sync.Pool当中   ppFree.Put(p)}

    基于以上总结,如果sync.Pool中存储的对象占据的内存大小是不固定的话,此时需要注意放入对象的大小,防止内存泄漏或者内存浪费。

    4.2 不要往sync.Pool中放入数据库连接/tcp连接

    TCP连接和数据库连接等资源的获取和释放通常需要遵循一定的规范,比如需要在连接完成后显式地关闭连接等,这些规范是基于网络协议、数据库协议等规范而制定的,如果这些规范没有被正确遵守,就可能导致连接泄漏、连接池资源耗尽等问题。

    当使用 sync.Pool 存储连接对象时,如果这些连接对象并没有显式的关闭,那么它们就会在内存中一直存在,直到进程结束。如果连接对象数量过多,那么这些未关闭的连接对象就会占用过多的内存资源,导致内存泄漏等问题。

    举个例子,假设有一个对象Conn表示数据库连接,它的Close方法用于关闭连接。如果将Conn对象放入sync.Pool中,并在从池中取出并使用后没有手动调用Close方法归还对象,那么这些连接就会一直保持打开状态,直到程序退出或达到连接数限制等情况。这可能会导致资源耗尽或其他一些问题。

    以下是一个简单的示例代码,使用 sync.Pool 存储TCP连接对象,演示了连接对象泄漏的情况:

    import (   "fmt"   "net"   "sync"   "time")var pool = &sync.Pool{   New: func() interface{} {      conn, err := net.Dial("tcp", "localhost:8000")      if err != nil {         panic(err)      }      return conn   },}func main() {   // 模拟使用连接   for i := 0; i < 100; i++ {      conn := pool.Get().(net.Conn)      time.Sleep(100 * time.Millisecond)      fmt.Fprintf(conn, "GET / Http/1.0\r\n\r\n")      // 不关闭连接      // 不在使用连接时,释放连接对象到池中即可      pool.Put(conn)   }}

    在上面的代码中,我们使用 net.Dial 创建了一个 TCP 连接,并将其存储到 sync.Pool 中。在模拟使用连接时,我们从池中获取连接对象,向服务器发送一个简单的 HTTP 请求,然后将连接对象释放到池中。但是,我们没有显式地关闭连接对象。如果连接对象的数量很大,那么这些未关闭的连接对象就会占用大量的内存资源,导致内存泄漏等问题。

    因此,对于数据库连接或者TCP连接这种资源的释放需要遵循一定的规范,此时不应该使用sync.Pool来复用,可以自己实现数据库连接池等方式来实现连接的复用。

    “Go语言中的sync.Pool怎么使用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

    --结束END--

    本文标题: Go语言中的sync.Pool怎么使用

    本文链接: https://www.lsjlt.com/news/356252.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • Go语言中的sync.Pool怎么使用
      本篇内容介绍了“Go语言中的sync.Pool怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. 简介本文将介绍 Go 语言中的 s...
      99+
      2023-07-05
    • 深入了解Go语言中sync.Pool的使用
      目录1. 简介2. 问题引入2.1 问题描述2.2 解决方案3. 基本使用3.1 使用方式3.2 使用例子    4.使用注意事项4.1 需要...
      99+
      2023-05-15
      Go语言 sync.Pool使用 Go语言 sync.Pool Go sync.Pool
    • Go语言中的iota怎么使用
      今天小编给大家分享一下Go语言中的iota怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一...
      99+
      2024-04-02
    • go语言中的decimal怎么使用
      这篇文章主要介绍了go语言中的decimal怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇go语言中的decimal怎么使用文章都会有所收获,下面我们一起来看看吧。decimal是为了解决Golang中...
      99+
      2023-07-05
    • GoLang sync.Pool怎么使用
      这篇文章主要讲解了“GoLang sync.Pool怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“GoLang sync.Pool怎么使用”吧!使用场景一句话总结...
      99+
      2023-07-04
    • go语言中怎么使用select
      这篇文章主要介绍“go语言中怎么使用select”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“go语言中怎么使用select”文章能帮助大家解决问题。在golang语言中,select语句...
      99+
      2023-06-30
    • Golang中的sync.Pool怎么用
      这篇文章主要讲解了“Golang中的sync.Pool怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang中的sync.Pool怎么用”吧!原理分析1.1 结构依赖关系图下面是相...
      99+
      2023-07-05
    • go语言中mysql怎么使用
      本篇内容介绍了“go语言中mysql怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!golang支持多种数据库MySQLMySQL是一...
      99+
      2023-07-04
    • Go语言中的事务怎么使用
      本篇内容主要讲解“Go语言中的事务怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go语言中的事务怎么使用”吧!事务实践服务端在进行和数据库交互时,对于一些场景我们可能会使用事务来保证数据...
      99+
      2023-07-06
    • Go语言中defer语句怎么使用
      今天小编给大家分享一下Go语言中defer语句怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.在一个函数内的def...
      99+
      2023-07-02
    • Go语言中的链表怎么使用
      本篇内容主要讲解“Go语言中的链表怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go语言中的链表怎么使用”吧!1. 什么是链表链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的...
      99+
      2023-06-30
    • Go语言中的闭包怎么使用
      这篇文章主要讲解了“Go语言中的闭包怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go语言中的闭包怎么使用”吧!闭包基本介绍闭包就是 一个函数 和其相关的&nbs...
      99+
      2023-07-04
    • Go语言中的接口怎么使用
      今天小编给大家分享一下Go语言中的接口怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。接口在 Go 语言中,接口是一种...
      99+
      2023-07-04
    • Go语言中的包Package怎么使用
      本文小编为大家详细介绍“Go语言中的包Package怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言中的包Package怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。问题一、Go使用Pac...
      99+
      2023-07-02
    • Go语言中的条件语句怎么使用
      这篇文章主要介绍“Go语言中的条件语句怎么使用”,在日常操作中,相信很多人在Go语言中的条件语句怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言中的条件语句怎么使用”的疑惑有所帮助!接下来,请跟...
      99+
      2023-06-30
    • Go语言中的:=怎么用
      这篇文章主要为大家展示了“Go语言中的:=怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Go语言中的:=怎么用”这篇文章吧。1. 单变量 :=Go 语言中新增了一个特殊的运算符:=,这个运...
      99+
      2023-06-22
    • 怎么使用GO语言
      这篇文章主要讲解了“怎么使用GO语言”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么使用GO语言”吧!什么是 GVMGo 语言版本管理器(GVM)是管理 Go 语言环境的开源工具。GVM ...
      99+
      2023-06-16
    • go语言中for range怎么使用
      这篇文章主要介绍“go语言中for range怎么使用”,在日常操作中,相信很多人在go语言中for range怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”go语言中for range怎么使用”的疑...
      99+
      2023-07-04
    • Go语言的exec怎么使用
      本篇内容介绍了“Go语言的exec怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Exec 是 os 包中的一个子包,它可用于使用 G...
      99+
      2023-06-30
    • go语言中怎么使用openssl库
      在Go语言中使用OpenSSL库可以通过调用C语言的接口来实现。首先需要安装CGO工具,然后在Go代码中引入C语言的头文件和库文件,...
      99+
      2024-04-09
      go语言 openssl
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作