设为首页 加入收藏

TOP

sync.Pool:提高Go语言程序性能的关键一步(二)
2023-07-23 13:30:30 】 浏览:57
Tags:sync.Pool 提高 程序性 能的关
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 []byte

type pp struct {
   buf buffer
   // 省略掉其他不相关的字段
}

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

Sprintf方法完成之后,重新将pp结构体放入sync.Pool当中,此时pp结构体中的

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇快速搭建一个go语言web后端服务脚.. 下一篇Go语言入门5(map 哈希表)

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目