文档章节

同步之sync.Pool临时对象池

秋风醉了
 秋风醉了
发布于 2016/08/05 19:14
字数 1390
阅读 207
收藏 0
点赞 0
评论 0

同步之sync.Pool临时对象池

当多个goroutine都需要创建同一个对象的时候,如果goroutine过多,可能导致对象的创建数目剧增。 而对象又是占用内存的,进而导致的就是内存回收的GC压力徒增。造成“并发大-占用内存大-GC缓慢-处理并发能力降低-并发更大”这样的恶性循环。** 在这个时候,我们非常迫切需要有一个对象池,每个goroutine不再自己单独创建对象,而是从对象池中获取出一个对象(如果池中已经有的话)。 **这就是sync.Pool出现的目的了。

类型sync.Pool有两个公开的方法。一个是Get,另一个是Put。前者的功能是从池中获取一个interface{}类型的值,而后者的作用则是把一个interface{}类型的值放置于池中。

由于Pool在使用时可能会在多个goroutine之间交换对象,所以比较复杂。我们先来看一下数据结构:

type Pool struct {
	local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
	localSize uintptr        // size of the local array

	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	// It may not be changed concurrently with calls to Get.
	New func() interface{}
}

// Local per-P Pool appendix.
type poolLocal struct {
	private interface{}   // Can be used only by the respective P.
	shared  []interface{} // Can be used by any P.
	Mutex                 // Protects shared.
	pad     [128]byte     // Prevents false sharing.
}

获取对象过程是:

1)固定到某个P,尝试从私有对象获取,如果私有对象非空则返回该对象,并把私有对象置空;

2)如果私有对象是空的时候,就去当前子池的共享列表获取(需要加锁);

3)如果当前子池的共享列表也是空的,那么就尝试去其他P的子池的共享列表偷取一个(需要加锁);

4)如果其他子池都是空的,最后就用用户指定的New函数产生一个新的对象返回。

可以看到一次get操作最少0次加锁,最大N(N等于MAXPROCS)次加锁。

归还对象的过程:

1)固定到某个P,如果私有对象为空则放到私有对象;

2)否则加入到该P子池的共享列表中(需要加锁)。

可以看到一次put操作最少0次加锁,最多1次加锁。

由于goroutine具体会分配到那个P执行是golang的协程调度系统决定的,因此在MAXPROCS>1的情况下,多goroutine用同一个sync.Pool的话,各个P的子池之间缓存的对象是否平衡以及开销如何是没办法准确衡量的。但如果goroutine数目和缓存的对象数目远远大于MAXPROCS的话,概率上说应该是相对平衡的。

总的来说,sync.Pool的定位不是做类似连接池的东西,它的用途仅仅是增加对象重用的几率,减少gc的负担,而开销方面也不是很便宜的。

Pool的清空: 在每次GC之前,runtime会调用poolCleanup函数来将Pool所有的指针变为nil,计数变为0,这样原本Pool中储存的对象会被GC全部回收。这个特性使得Pool有自己独特的用途。首先,有状态的对象绝不能储存在Pool中,Pool不能用作连接池。其次,你不需要担心Pool会不会一直增长,因为runtime定期帮你回收Pool中的数据。但是也不能无限制地向Pool中Put新的对象,这样会拖累GC,也违背了Pool的设计初衷。官方的说法是Pool适用于储存一些会在goroutine间分享的临时对象,举的例子是fmt包中的输出缓冲区。

示例如下,

package main

import (
	"sync"
	"fmt"
	"net/http"
	"io"
	"log"
)

// 临时对象池
var p = sync.Pool{
	New: func() interface{} {
		buffer := make([]byte, 256)
		return &buffer
	},
}

//wg 是一个指针类型,必须是一个内存地址
func readContent(wg *sync.WaitGroup) {
	defer wg.Done()
	resp, err := http.Get("http://my.oschina.net/xinxingegeya/home")
	if err != nil {
		// handle error
	}

	defer resp.Body.Close()

	byteSlice := p.Get().(*[]byte)  //类型断言

	numBytesReadAtLeast, err := io.ReadFull(resp.Body, *byteSlice)
	if err != nil {
		// handle error
	}

	p.Put(byteSlice)

	log.Printf("Number of bytes read: %d\n", numBytesReadAtLeast)
	fmt.Println(string((*byteSlice)[:256]))
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go readContent(&wg)
	}

	wg.Wait()
	fmt.Println("end...")
}

通过sync.Pools实现了对象的复用。可以通过下面这个程序来验证。如果不用goroutine,那么需要在内存空间new1000个字节切片,而现在使用sync.Pool,需要new的字节切片远远小于1000,如下,

package main

import (
	"sync"
	"fmt"
	"net/http"
	"io"
	"log"
)

var mu sync.Mutex
var holder map[string]bool = make(map[string]bool)

// 临时对象池
var p = sync.Pool{
	New: func() interface{} {
		buffer := make([]byte, 256)
		return &buffer
	},
}

//wg 是一个指针类型,必须是一个内存地址
func readContent(wg *sync.WaitGroup) {
	defer wg.Done()
	resp, err := http.Get("http://my.oschina.net/xinxingegeya/home")
	if err != nil {
		// handle error
	}

	defer resp.Body.Close()

	byteSlice := p.Get().(*[]byte)  //类型断言

	key := fmt.Sprintf("%p", byteSlice)
	////////////////////
	// 互斥锁,实现同步操作
	mu.Lock()
	_, ok := holder[key]
	if !ok {
		holder[key] = true
	}
	mu.Unlock()
	////////////////////

	numBytesReadAtLeast, err := io.ReadFull(resp.Body, *byteSlice)
	if err != nil {
		// handle error
	}

	p.Put(byteSlice)

	log.Printf("Number of bytes read: %d\n", numBytesReadAtLeast)
	fmt.Println(string((*byteSlice)[:256]))
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go readContent(&wg)
	}

	wg.Wait()

	fmt.Println(len(holder))

	for key, val := range holder {
		fmt.Println("Key:", key, "Value:", val)
	}

	fmt.Println("end...")
}

结果,

20
Key: 0xc820cd4c20 Value: true
Key: 0xc820e21f60 Value: true
Key: 0xc820e05860 Value: true
Key: 0xc8201f0b00 Value: true
Key: 0xc820a60440 Value: true
Key: 0xc820e05b20 Value: true
Key: 0xc820362840 Value: true
Key: 0xc8204423c0 Value: true
Key: 0xc820442960 Value: true
Key: 0xc82043eea0 Value: true
Key: 0xc8201f18e0 Value: true
Key: 0xc8201f1300 Value: true
Key: 0xc820ec00c0 Value: true
Key: 0xc82031b8a0 Value: true
Key: 0xc820e04f20 Value: true
Key: 0xc820e20920 Value: true
Key: 0xc8204420e0 Value: true
Key: 0xc82043e640 Value: true
Key: 0xc820aa91a0 Value: true
Key: 0xc820cd5b20 Value: true
end...

=======END=======

© 著作权归作者所有

共有 人打赏支持
秋风醉了
粉丝 223
博文 581
码字总数 411013
作品 0
东城
程序员
golang sync.Pool试用说明及注意事项

Go tip 是 Go 语言的实验分支,包含了很多尚在讨论,但很有可能会加入 stable 分支的特性。“Go tip 在做什么”(原文地址:What's happening in Go tip)分析总结了 Go 语言尚在开发中的一些...

wkh ⋅ 2014/06/20 ⋅ 0

golang 核心开发者 Dmitry Vyukov(1.1 调度器作者) 关于性能剖析

让我们假设你有一golang 程序,想改善其性能。有几种工具可以帮我们完成这个任务。这些工具可以帮我们识别程序中的热点(cpu,io,memory), 热点即是那些需要我们集中精力于其上,能显著改善改善...

yujian0231 ⋅ 2015/01/26 ⋅ 1

[转载]golang sync.Pool

Go 1.3 的sync包中加入一个新特性:Pool。 官方文档可以看这里http://golang.org/pkg/sync/#Pool 这个类设计的目的是用来保存和复用临时对象,以减少内存分配,降低CG压力。 Get返回Pool中的...

大蓝妹 ⋅ 2014/10/29 ⋅ 0

GO http server (III) 组建简易 HTTP Server 框架

上篇提到 DefaultServerMux 作为默认的 HTTP Server 框架太过简单,缺少很多功能。这篇我们利用官方库和一些三方库来定制一个简易合用的 HTTP Server 框架。完整代码见这里 Router 首先要有 ...

小小小超子 ⋅ 06/02 ⋅ 0

GO性能优化小结

1 内存优化 1.1 小对象合并成结构体一次分配,减少内存分配次数 做过C/C++的同学可能知道,小对象在堆上频繁地申请释放,会造成内存碎片(有的叫空洞),导致分配大的对象时无法申请到连续的...

John ⋅ 01/02 ⋅ 0

(Hibernate)Session接口详解

Session接口是Hibernate应用使用最广泛的接口,Session也被称为持久化管理器。Session对象是Hibernate技术的核心,持久化化对象的生命周期、事务的管理和持久化对象的查询、更新和删除都是通...

iborder ⋅ 2016/11/01 ⋅ 0

Hibernate学习:二(持久化对象的生命周期),有参考其他资料

持久化对象在整个hibernate框架中运行,一共有四种不同的状态产生: 临时状态(Transient Objects):通过 new 创建的对象,是临时对象,如果临时对象不做任何操作的话,会被gc回收,说明这种...

这个人很懒-啥都没写 ⋅ 2013/12/08 ⋅ 0

Servlet 单例多线程

深入研究Servlet线程安全性问题 http://www.cnblogs.com/gw811/archive/2012/09/07/2674859.html Servlet 单例多线程 Servlet如何处理多个请求访问? Servlet容器默认是采用单实例多线程的方...

城固如春 ⋅ 2016/10/23 ⋅ 0

Java学习资料-Servlet单例多线程

Servlet 单例多线程 Servlet如何处理多个请求访问? Servlet容器默认是采用单实例多线程的方式处理多个请求的: 1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并...

晓阳 ⋅ 2015/02/26 ⋅ 0

Servlet 单例多线程

Servlet 单例多线程 Servlet如何处理多个请求访问? Servlet容器默认是采用单实例多线程的方式处理多个请求的: 1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并...

吴海宏 ⋅ 2015/01/21 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vim编辑模式、命令模式

编辑模式 vim要从一般模式进入编辑模式只要按字母 i 、I、a、A、o、O键就可以了 要从编辑模式回到一般模式按键盘上的Esc键即可。 按键 作用 i 在当前字符前插入 I 在光标所在行的行首插入 o ...

黄昏残影 ⋅ 24分钟前 ⋅ 0

OSChina 周五乱弹 —— 如果有一天不当程序员了

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @guanglun :分享off的单曲《我唱情歌给你听》 《我唱情歌给你听》- off 手机党少年们想听歌,请使劲儿戳(这里) @小小编辑 :#如果不做程序...

小小编辑 ⋅ 31分钟前 ⋅ 4

从 Confluence 5.3 及其早期版本中恢复空间

如果你需要从 Confluence 5.3 及其早期版本中的导出文件恢复到晚于 Confluence 5.3 的 Confluence 中的话。你可以使用临时的 Confluence 空间安装,然后将这个 Confluence 安装实例升级到你现...

honeymose ⋅ 今天 ⋅ 0

Java8新增的DateTimeFormatter与SimpleDateFormat的区别

两者最大的区别是,Java8的DateTimeFormatter也是线程安全的,而SimpleDateFormat并不是线程安全。 在并发环境下使用SimpleDateFormat 为了能够在多线程环境下使用SimpleDateFormat,有这三种...

人觉非常君 ⋅ 今天 ⋅ 0

多线程如何控制执行顺序

线程的生命周期说明: 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、...

MarinJ_Shao ⋅ 今天 ⋅ 0

用ZBLOG2.3博客写读书笔记网站能创造今日头条的辉煌吗?

最近两年,著名的自媒体网站今日头条可以说是火得一塌糊涂,虽然从目前来看也遇到了一点瓶颈,毕竟发展到了一定的规模,继续增长就更加难了,但如今的今日头条规模和流量已经非常大了。 我们...

原创小博客 ⋅ 今天 ⋅ 0

MyBatis四大核心概念

本文讲解 MyBatis 四大核心概念(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:Sql...

waylau ⋅ 今天 ⋅ 0

以太坊java开发包web3j简介

web3j(org.web3j)是Java版本的以太坊JSON RPC接口协议封装实现,如果需要将你的Java应用或安卓应用接入以太坊,或者希望用java开发一个钱包应用,那么用web3j就对了。 web3j的功能相当完整...

汇智网教程 ⋅ 今天 ⋅ 0

2个线程交替打印100以内的数字

重点提示: 线程的本质上只是一个壳子,真正的逻辑其实在“竞态条件”中。 举个例子,比如本题中的打印,那么在竞态条件中,我只需要一个方法即可; 假如我的需求是2个线程,一个+1,一个-1,...

Germmy ⋅ 今天 ⋅ 0

Django第一期

安装Django 去https://www.djangoproject.com/download/ 下载最新版的Django,然后解压放到Anaconda\Lib\site-packages目录下,然后cmd进入此目录,输入安装命令: python setup.py install ...

大不了敲一辈子代码 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部