广告
返回顶部
首页 > 资讯 > 后端开发 > GO >深入Golang中的sync.Pool详解
  • 612
分享到

深入Golang中的sync.Pool详解

2024-04-02 19:04:59 612人浏览 泡泡鱼
摘要

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

我们通常用golang来构建高并发场景下的应用,但是由于Golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池。 sync.Pool是可伸缩的,并发安全的。其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器。 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取。

任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩。

sync.Pool首先声明了两个结构体


// Local per-P Pool appendix.
type poolLocalInternal struct {
	private interface{}   // Can be used only by the respective P.
	shared  []interface{} // Can be used by any P.
	Mutex                 // Protects shared.
}

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中高效的使用goroutine,sync.Pool为每个P(对应CPU)都分配一个本地池,当执行Get或者Put操作的时候,会先将goroutine和某个P的子池关联,再对该子池进行操作。 每个P的子池分为私有对象和共享列表对象,私有对象只能被特定的P访问,共享列表对象可以被任何P访问。因为同一时刻一个P只能执行一个goroutine,所以无需加,但是对共享列表对象进行操作时,因为可能有多个goroutine同时操作,所以需要加锁。

值得注意的是poolLocal结构体中有个pad成员,目的是为了防止false sharing。cache使用中常见的一个问题是false sharing。当不同的线程同时读写同一cache line上不同数据时就可能发生false sharing。false sharing会导致多核处理器上严重的系统性能下降。具体的可以参考伪共享(False Sharing)。

类型sync.Pool有两个公开的方法,一个是Get,一个是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
	}
	runtime_procUnpin()
	if x != nil {
		l.Lock()
		l.shared = append(l.shared, x)
		l.Unlock()
	}
	if race.Enabled {
		race.Enable()
	}
}

如果放入的值为空,直接return.检查当前goroutine的是否设置对象池私有值,如果没有则将x赋值给其私有成员,并将x设置为nil。如果当前goroutine私有值已经被设置,那么将该值追加到共享列表。


func (p *Pool) Get() interface{} {
	if race.Enabled {
		race.Disable()
	}
	l := p.pin()
	x := l.private
	l.private = nil
	runtime_procUnpin()
	if x == nil {
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
		}
		l.Unlock()
		if x == nil {
			x = p.getSlow()
		}
	}
	if race.Enabled {
		race.Enable()
		if x != nil {
			race.Acquire(poolRaceAddr(x))
		}
	}
	if x == nil && p.New != nil {
		x = p.New()
	}
	return x
}
  1. 尝试从本地P对应的那个本地池中获取一个对象值, 并从本地池冲删除该值。
  2. 如果获取失败,那么从共享池中获取, 并从共享队列中删除该值。
  3. 如果获取失败,那么从其他P的共享池中偷一个过来,并删除共享池中的该值(p.getSlow())。
  4. 如果仍然失败,那么直接通过New()分配一个返回值,注意这个分配的值不会被放入池中。New()返回用户注册的New函数的值,如果用户未注册New,那么返回nil。

最后我们来看一下init函数。


func init() {
    runtime_reGISterPoolCleanup(poolCleanup)
}

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


package main

import (
    "sync"
    "time"
    "fmt"
)

var bytePool = sync.Pool{
    New: func() interface{} {
        b := make([]byte, 1024)
        return &b
    },
}


func main()  {
    //defer
    //debug.SetGCPercent(debug.SetGCPercent(-1))
    a := time.Now().Unix()
    for i:=0;i<1000000000;i++{
        obj := make([]byte, 1024)
        _ = obj
    }
    b := time.Now().Unix()

    for j:=0;j<1000000000;j++  {
        obj := bytePool.Get().(*[]byte)
        _ = obj
        bytePool.Put(obj)
    }

    c := time.Now().Unix()
    fmt.Println("without pool ", b - a, "s")
    fmt.Println("with    pool ", c - b, "s")
}

可见GC对性能影响不大,因为shared list太长也会耗时。

总结

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

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

在Golang中如果要实现连接池的效果,可以用container/list来实现,开源界也有一些现成的实现,比如go-commons-pool,具体的读者可以去自行了解。

参考资料:

go语言的官方包sync.Pool的实现原理和适用场景

sync.Pool源码

到此这篇关于深入Golang中的sync.Pool详解的文章就介绍到这了,更多相关Golang sync.Pool内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: 深入Golang中的sync.Pool详解

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

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

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

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

下载Word文档
猜你喜欢
  • 深入Golang中的sync.Pool详解
    我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池。 ...
    99+
    2022-11-12
  • 深入浅出Golang中的sync.Pool
    目录一、原理分析1.1 结构依赖关系图1.2 用图让代码说话1.3 Put过程分析二、学习收获2.1 如何自己实现一个无锁队列学习到的内容: 1.一个64位的int类型值,充分利用高...
    99+
    2023-03-13
    Golang sync.Pool原理 Golang sync.Pool使用 Golang sync.Pool
  • Golang之sync.Pool使用详解
    前言 我们通常用 Golang 来开发并构建高并发场景下的服务,但是由于 Golang 内建的GC机制多少会影响服务的性能,因此,为了减少频繁GC,Golang提供了对象重用的机制...
    99+
    2022-11-12
  • 深入了解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
  • Golang中的sync.Pool怎么用
    这篇文章主要讲解了“Golang中的sync.Pool怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang中的sync.Pool怎么用”吧!原理分析1.1 结构依赖关系图下面是相...
    99+
    2023-07-05
  • 深入解析Sync.Pool如何提升Go程序性能
    目录1. Sync.Pool 简介2. Sync.Pool 的概念3. Sync.Pool 的使用3.1 创建 Sync.Pool 对象3.2 获取和放回对象4. Sync.Pool...
    99+
    2023-05-18
    Go Sync.Pool提升性能 Go Sync.Pool用法 Go Sync.Pool
  • 深入研究:go和golang的差异详解
    深入探究:Go 和 Golang 之间的差异随着现代技术的飞速发展和应用需求的不断增长,编程语言成为了开发者们必备的工具。近年来,Go(也被称为Golang)在编程界引起了广泛的关注和讨论。然而,对于初学者和有经验的开发者来说,Go 和 G...
    99+
    2023-12-29
    差异比较:go vs golang
  • Golang中结构体映射mapstructure库深入详解
    目录mapstructure库字段标签内嵌结构未映射字段Metadata弱类型输入逆向转换解码器示例在数据传递时,需要先编解码;常用的方式是JSON编解码(参见《golang之JSO...
    99+
    2023-01-02
    Go结构体映射mapstructure Go mapstructure Go结构体映射
  • 深入解析golang bufio
    目录bufio 包介绍 golang bufio使用bufio进行写缓存中满数据缓存中仍有空间待写入的数据大于缓存的大小缓存重用获取缓存的可用空间数使用bufio进行读Pe...
    99+
    2022-11-13
  • 深入了解Golang中的run方法
    Go是一种快速,可靠和开源的编程语言。Go语言通过其高效的并发性和垃圾回收器以及C的速度,用于构建高效和可扩展的网络服务器和系统编程。让我们深入了解Golang中的run方法。run()方法是golang中重要的一种方法,可以用于创建新的协...
    99+
    2023-05-14
  • 深入了解golang中的的泛型(Generic)
    本篇文章给大家带来的内容是介绍深入理解golang中的泛型?泛型怎么使用?有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助。什么是泛型泛型(Generic)是一种编程技术。在强类型语言中, 允许编写代码时使用以后才指定的类型, ...
    99+
    2023-05-14
    Go 后端
  • 深入解析golang中的标准库flag
    Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单。 os.Args 如果你只是简单的想要获取命令行参数,可以像下面的代码示例一样使用os.Args...
    99+
    2022-11-12
  • 深入了解golang中的静态方法
    Golang是一门支持面向对象编程的编程语言,但是它的面向对象编程方式有些不同于传统的面向对象编程语言。有一点不同是Golang中没有类(class)的概念,也没有Java中的静态方法(static method)。但是Golang中提供了...
    99+
    2023-05-14
  • 深入了解Golang中的数据类型
    目录1. 基本数据类型1.1 整数型1.2 Golang 提供了两种浮点型数据类型:1.3 布尔型1.4 字符型1.5 字符串型2. 复合数据类型2.1 数组2.2 切片2.3 字符...
    99+
    2023-05-18
    Golang数据类型使用 Golang数据类型 Go 数据类型
  • 深入详解React中的ref
    对于 Ref 理解与使用,一些读者可能还停留在用 ref 获取真实 DOM 元素和获取类组件实例层面上其实 ref 除了这两项常用功能之外,还有很多别的小技巧通过本篇文章的学习,你将收获 React ref 的基本和进阶用法,并且能够明白 ...
    99+
    2023-05-14
    ref React
  • 深入解析Golang中JSON的编码与解码
    目录1. JSON 简介2. Golang 中的 JSON 编码2.1 结构体的 JSON 编码2.2 切片和映射的 JSON 编码3. Golang 中的 JSON 解码3.1 J...
    99+
    2023-05-19
    Golang JSON编码 解码 Golang JSON编码 Golang JSON解码 Golang JSON
  • Golang中map的深入探究
    目录简介Map 的底层内存模型Map 的存与取底层代码Map 的扩容第一种情况第二种情况Map 的有序性Map 的并发总结简介 本文主要通过探究在golang 中map的数据结构及源...
    99+
    2022-11-11
  • 深入聊聊Golang中的sync.Cond
    本文将介绍 Go 语言中的 sync.Cond 并发原语,包括 sync.Cond的基本使用方法、实现原理、使用注意事项以及常见的使用使用场景。能够更好地理解和应用 Cond 来实现 goroutine 之间的同步。1. 基本使用1.1 定...
    99+
    2023-05-14
    后端 Go
  • 深入了解Golang中的格式化输出
    目录fmt格式化整数格式化浮点数格式化布尔类型格式化字符格式化字符串格式化指针格式化通用占位符宽度表示fmt Go语言用于控制文本输出常用的标准库是fmt fmt中主要用于输出的函数...
    99+
    2022-11-21
    Golang格式化输出 Golang格式化
  • 深入了解Golang中的Slice底层实现
    目录1 Go数组2 切片的数据结构3 创建切片3.1 方法一:make3.2 方法二:字面量4 nil和空切片5 切片扩容5.1 扩容策略5.2 底层数组是不是新地址range遍历数...
    99+
    2023-02-26
    Golang Slice实现 Golang Slice Go Slice
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作