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语言map与string的相互转换的实现
Apr 07 Golang
golang协程池模拟实现群发邮件功能
May 02 Golang
Golang实现AES对称加密的过程详解
May 20 Golang
golang 实用库gotable的具体使用
Jul 01 Golang
K8s部署发布Golang应用程序的实现方法
Jul 16 Golang
Go语言特点及基本数据类型使用详解
Mar 21 Golang
Golang原生rpc(rpc服务端源码解读)
Apr 07 Golang
Golang 结构体数据集合
Apr 22 Golang
Golang 入门 之url 包
May 04 Golang
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
Jun 21 Golang
Go语言怎么使用变长参数函数
Jul 15 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
ThinkPHP采用模块和操作分析
2011/04/18 PHP
PHP中几个常用的魔术常量
2012/02/23 PHP
PHP中使用cURL实现Get和Post请求的方法
2013/03/13 PHP
Yii2 rbac权限控制操作步骤实例教程
2016/04/29 PHP
PHP各种常见经典算法总结【排序、查找、翻转等】
2019/08/05 PHP
用javascript实现分割提取页面所需内容
2007/05/09 Javascript
js身份证验证超强脚本
2008/10/26 Javascript
自己写了一个展开和收起的多更能型的js效果
2013/03/05 Javascript
jQuery多条件筛选如何实现
2015/11/04 Javascript
详解Javacript和AngularJS中的Promises
2016/02/09 Javascript
jQuery模拟select实现下拉菜单功能
2016/06/20 Javascript
JS实现类似51job上的地区选择效果示例
2016/11/17 Javascript
AngularJS实现表格的增删改查(仅限前端)
2017/07/04 Javascript
详解Nodejs mongoose
2018/06/10 NodeJs
bootstrap 日期控件 datepicker被弹出框dialog覆盖的解决办法
2019/07/09 Javascript
微信小程序去除左上角返回键的实现方法
2020/03/06 Javascript
[06:50]DSPL次级职业联赛十强晋级之路
2014/11/18 DOTA
python实现udp数据报传输的方法
2014/09/26 Python
在Linux上安装Python的Flask框架和创建第一个app实例的教程
2015/03/30 Python
Python根据区号生成手机号码的方法
2015/07/08 Python
win10系统下Anaconda3安装配置方法图文教程
2018/09/19 Python
Python中实例化class的执行顺序示例详解
2018/10/14 Python
VPS CENTOS 上配置python,mysql,nginx,uwsgi,django的方法详解
2019/07/01 Python
python3.6+selenium实现操作Frame中的页面元素
2019/07/16 Python
Python 绘制可视化折线图
2020/07/22 Python
详解python安装matplotlib库三种失败情况
2020/07/28 Python
Python创建简单的神经网络实例讲解
2021/01/04 Python
日语翻译个人求职的自我评价
2013/10/14 职场文书
生物学学生自我评价
2014/01/17 职场文书
国培计划培训感言
2014/03/11 职场文书
企业业务员岗位职责
2014/03/14 职场文书
工伤赔偿协议书
2014/04/15 职场文书
夫妻忠诚协议书范本
2014/11/17 职场文书
学校2015年纠风工作总结
2015/05/15 职场文书
年会主持人开场白台词
2015/05/29 职场文书
红歌会主持词
2015/07/02 职场文书