Golang之sync.Pool使用详解


Posted in Golang onMay 06, 2021

前言

我们通常用 Golang 来开发并构建高并发场景下的服务,但是由于 Golang 内建的GC机制多少会影响服务的性能,因此,为了减少频繁GC,Golang提供了对象重用的机制,也就是使用sync.Pool构建对象池。

sync.Pool介绍

首先sync.Pool是可伸缩的临时对象池,也是并发安全的。其可伸缩的大小会受限于内存的大小,可以理解为是一个存放可重用对象的容器。sync.Pool设计的目的就是用于存放已经分配的但是暂时又不用的对象,而且在需要用到的时候,可以直接从该pool中取。

pool中任何存放的值可以在任何时候被删除而不会收到通知。另外,在高负载下pool对象池可以动态的扩容,而在不使用或者说并发量不高时对象池会收缩。关键思想就是对象的复用,避免重复创建、销毁,从而影响性能。

个人觉得它的名字有一定的误导性,因为 Pool 里装的对象可以被无通知地被回收,觉得 sync.Cache 的名字更合适sync.Pool的命名。

sync.Pool首先声明了两个结构体,如下:

// Local per-P Pool appendix.
type poolLocalInternal struct {
  private interface{} // Can be used only by the respective P.
  shared  poolChain   // Local P can pushHead/popHead; any P can popTail.
}

type poolLocal struct {
  poolLocalInternal

  // Prevents false sharing on widespread platforms with
  // 128 mod (cache line size) = 0 .
  pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}

为了使得可以在多个goroutine中高效的使用并发,sync.Pool会为每个P(对应CPU,这里有点像GMP模型)都分配一个本地池,当执行Get或者Put操作的时候,会先将goroutine和某个P的对象池关联,再对该池进行操作。

每个P的对象池分为私有对象和共享列表对象,私有对象只能被特定的P访问,共享列表对象可以被任何P访问。因为同一时刻一个P只能执行一个goroutine,所以无需加锁,但是对共享列表对象进行操作时,因为可能有多个goroutine同时操作,即并发操作,所以需要加锁。

需要注意的是 poolLocal 结构体中有个 pad 成员,其目的是为了防止false sharing。cache使用中常见的一个问题是false sharing。当不同的线程同时读写同一个 cache line上不同数据时就可能发生false sharing。false sharing会导致多核处理器上严重的系统性能下降。具体的解释说明这里就不展开赘述了。

sync.Pool的Put和Get方法

sync.Pool 有两个公开的方法,一个是Get,另一个是Put。

Put方法

我们先来看一下Put方法的源码,如下:

// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
  if x == nil {
    return
  }
  if race.Enabled {
    if fastrand()%4 == 0 {
      // Randomly drop x on floor.
      return
    }
    race.ReleaseMerge(poolRaceAddr(x))
    race.Disable()
  }
  l, _ := p.pin()
  if l.private == nil {
    l.private = x
    x = nil
  }
  if x != nil {
    l.shared.pushHead(x)
  }
  runtime_procUnpin()
  if race.Enabled {
    race.Enable()
  }
}

阅读以上Put方法的源码可以知道:

  • 如果Put放入的值为空,则直接 return 了,不会执行下面的逻辑了;
  • 如果不为空,则继续检查当前goroutine的private是否设置对象池私有值,如果没有则将x赋值给该私有成员,并将x设置为nil;
  • 如果当前goroutine的private私有值已经被赋值过了,那么将该值追加到共享列表。

Get方法

我们再来看下Get方法的源码,如下:

func (p *Pool) Get() interface{} {
  if race.Enabled {
    race.Disable()
  }
  l, pid := p.pin()
  x := l.private
  l.private = nil
  if x == nil {
    // Try to pop the head of the local shard. We prefer
    // the head over the tail for temporal locality of
    // reuse.
    x, _ = l.shared.popHead()
    if x == nil {
      x = p.getSlow(pid)
    }
  }
  runtime_procUnpin()
  if race.Enabled {
    race.Enable()
    if x != nil {
      race.Acquire(poolRaceAddr(x))
    }
  }
  if x == nil && p.New != nil {
    x = p.New()
  }
  return x
}

阅读以上Get方法的源码,可以知道:

  • 首先尝试从本地P对应的那个对象池中获取一个对象值, 并从对象池中删掉该值。
  • 如果从本地对象池中获取失败,则从共享列表中获取,并从共享列表中删除该值。
  • 如果从共享列表中获取失败,则会从其它P的对象池中“偷”一个过来,并删除共享池中的该值(就是源码中14行的p.getSlow())。
  • 如果还是失败,那么直接通过 New() 分配一个返回值,注意这个分配的值不会被放入对象池中。New()是返回用户注册的New函数的值,如果用户未注册New,那么默认返回nil。

init函数

最后我们来看一下init函数,如下:

func init() {
  funtime_registerPoolCleanup(poolCleanup)
}

可以看到在init的时候注册了一个PoolCleanup函数,他会清除掉sync.Pool中的所有的缓存的对象,这个注册函数会在每次GC的时候运行,所以sync.Pool中的值只在两次GC中间的时段有效。

sync.Pool使用示例

示例代码:

package main
import (
 "fmt"
 "sync"
)
// 定义一个 Person 结构体,有Name和Age变量
type Person struct {
 Name string
 Age int
}
// 初始化sync.Pool,new函数就是创建Person结构体
func initPool() *sync.Pool {
 return &sync.Pool{
  New: func() interface{} {
   fmt.Println("创建一个 person.")
   return &Person{}
  },
 }
}
// 主函数,入口函数
func main() {
 pool := initPool()
 person := pool.Get().(*Person)
 fmt.Println("首次从sync.Pool中获取person:", person)
 person.Name = "Jack"
 person.Age = 23
 pool.Put(person)
 fmt.Println("设置的对象Name: ", person.Name)
 fmt.Println("设置的对象Age: ", person.Age)
 fmt.Println("Pool 中有一个对象,调用Get方法获取:", pool.Get().(*Person))
 fmt.Println("Pool 中没有对象了,再次调用Get方法:", pool.Get().(*Person))
}

运行结果如下所示:

创建一个 person.
首次从sync.Pool中获取person:&{ 0}
设置的对象Name:  Jack
设置的对象Age:  23
Pool 中有一个对象,调用Get方法获取:&{Jack 23}
创建一个 person.
Pool 中没有对象了,再次调用Get方法: &{ 0}

总结

通过以上的源码及其示例,我们可以知道:

  • Get方法并不会对获取到的对象值做任何的保证,因为放入本地对象池中的值有可能会在任何时候被删除,而得不到通知。
  • 放入共享池中的值有可能被其他的goroutine拿走,所以对象池比较适合用来存储一些临时切状态无关的数据,但是不适合用来存储数据库连接的实例,因为存入对象池的值有可能会在垃圾回收时被删除掉,这违反了数据库连接池建立的初衷。

由此可知,Golang的对象池严格意义上来说是一个临时的对象池,适用于储存一些会在goroutine间分享的临时对象。主要作用是减少GC,提高性能。在Golang中最常见的使用场景就是fmt包中的输出缓冲区了。

代码Github归档地址: sync.Pool使用示例代码

到此这篇关于Golang之sync.Pool使用详解的文章就介绍到这了,更多相关Golang sync.Pool内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Golang 相关文章推荐
go:垃圾回收GC触发条件详解
Apr 24 Golang
golang如何去除多余空白字符(含制表符)
Apr 25 Golang
go语言求任意类型切片的长度操作
Apr 26 Golang
解决Golang time.Parse和time.Format的时区问题
Apr 29 Golang
对Golang中的FORM相关字段理解
May 02 Golang
golang协程池模拟实现群发邮件功能
May 02 Golang
go类型转换及与C的类型转换方式
May 05 Golang
GoLang中生成UUID唯一标识的实现
May 08 Golang
go语言基础 seek光标位置os包的使用
May 09 Golang
go web 预防跨站脚本的实现方式
Jun 11 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
Golang jwt身份认证
Apr 20 Golang
Golang 编译成DLL文件的操作
May 06 #Golang
完美解决golang go get私有仓库的问题
May 05 #Golang
golang gopm get -g -v 无法获取第三方库的解决方案
May 05 #Golang
go类型转换及与C的类型转换方式
May 05 #Golang
Golang: 内建容器的用法
May 05 #Golang
Go标准容器之Ring的使用说明
May 05 #Golang
go语言中GOPATH GOROOT的作用和设置方式
You might like
PHP中获取时间的下一周下个月的方法
2014/03/18 PHP
PHP遍历XML文档所有节点的方法
2015/03/12 PHP
php常用字符函数实例小结
2016/12/29 PHP
PHPExcel在linux环境下导出报500错误的解决方法
2017/01/26 PHP
Laravel如何自定义command命令浅析
2019/03/23 PHP
JSON 教程 json入门学习笔记
2020/09/22 Javascript
Javascript学习笔记之 函数篇(一) : 函数声明和函数表达式
2014/06/24 Javascript
node.js中的http.response.setHeader方法使用说明
2014/12/14 Javascript
JavaScript实现常用二级省市级联下拉列表的方法
2015/03/25 Javascript
jquery判断input值不为空的方法
2016/06/05 Javascript
jQuery实现iframe父窗体和子窗体的相互调用
2016/06/17 Javascript
Vue.js动态添加、删除选题的实例代码
2016/09/30 Javascript
详解springmvc 接收json对象的两种方式
2016/12/06 Javascript
JavaScript数据结构之二叉树的计数算法示例
2017/04/13 Javascript
微信小程序picker组件下拉框选择input输入框的实例
2017/09/20 Javascript
Bootstrap标签页(Tab)插件切换echarts不显示问题的解决
2018/07/13 Javascript
jQuery md5加密插件jQuery.md5.js用法示例
2018/08/24 jQuery
JS实现提示框跟随鼠标移动
2019/08/27 Javascript
Vue中jsx不完全应用指南小结
2019/11/01 Javascript
Vue 请求传公共参数的操作
2020/07/31 Javascript
[38:31]完美世界DOTA2联赛PWL S3 Magma vs GXR 第一场 12.13
2020/12/17 DOTA
Python字典操作简明总结
2015/04/13 Python
在Python中使用zlib模块进行数据压缩的教程
2015/06/26 Python
Python的pycurl包用法简介
2015/11/13 Python
对Python的多进程锁的使用方法详解
2019/02/18 Python
Python实现 版本号对比功能的实例代码
2019/04/18 Python
python过滤中英文标点符号的实例代码
2019/07/15 Python
python实现批量nii文件转换为png图像
2019/07/18 Python
python爬虫 urllib模块url编码处理详解
2019/08/20 Python
CSS3动画和HTML5新特性详解
2020/08/31 HTML / CSS
公司营业员的自我评价
2014/03/04 职场文书
财务部总监岗位职责
2014/03/12 职场文书
大学生自荐信怎么写
2015/03/26 职场文书
Python中Selenium对Cookie的操作方法
2021/07/09 Python
人民币符号
2022/02/17 杂记
AudioContext 实现音频可视化(web技术分享)
2022/02/24 Javascript