Go微服务实战
上QQ阅读APP看书,第一时间看更新

8.2.5 sync.Pool

Go语言是支持垃圾自动回收的。对于一些暂时用不到但是后续会用到的对象,为了提升性能,可以先暂存起来,这虽然会占用一些内存,但是比起销毁了再新建,要节省运行时间,这是典型的以空间换时间的思想。Go语言专门提供了暂存对象的工具,就是本节要介绍的sync.Pool。

sync.Pool是一个对象池,它是并发安全的,而且大小是可伸缩的,仅受限于内存。当需要使用对象的时候可以从对象池中直接取出使用。

注意

存入sync.Pool的对象可能会在不通知的情况下被释放,这一点一定要注意。比如一些socket长连接就不适合存入sync.Pool内。

下面看一下sync.Pool的结构体和方法:


type Pool struct {
    noCopy noCopy
    local unsafe.Pointer//本地缓冲池指针,每个处理器分配一个,
    //其类型是一个[P]poolLocal的数组
    localSize uintptr   //数组大小

    New func() interface{}//缓冲池中没有对象时,调用此方法构造一个
}

func (p *Pool) Get() interface{}
func (p *Pool) Put(interface{})

Get和Put是Pool的两个公共方法。Put方法是向池中添加对象,Get方法是从池中获取对象,如果没有对象则调用New方法创建生成,如果未设置New则返回nil。

在前面的“MPG”模型部分已经介绍过Go语言的重要组成结构M、P、G。Pool在运行时会为每个操作Pool的goroutine所关联的P都创建一个本地池。在执行Get方法的时候,会先从本地池获取,如果本地池没有则从其他P的本地池获取。这种特性让Pool的存储压力基于P进行了分摊。

接下来看一个sync.Pool的示例代码:


book/ch08/8.2/pool/pool.go
1. package main
2.
3. import (
4.     "fmt"
5.     "sync"
6.     "time"
7. )
8.
9. var byteSlicePool = sync.Pool{
10.     New: func() interface{} {
11.         b := make([]byte,1024)
12.         return &b
13.     },
14. }
15.
16. func main() {
17.     t1 := time.Now().Unix()
18.     //不使用Pool
19.     for i:=0;i<10000000000;i++{
20.         bytes := make([]byte,1024)
21.         _ = bytes
22.     }
23.     t2 := time.Now().Unix()
24.     //使用Pool
25.     for i:=0;i<10000000000;i++{
26.         bytes := byteSlicePool.Get().(*[]byte)
27.         _ = bytes
28.         byteSlicePool.Put(bytes)
29.     }
30.     t3 := time.Now().Unix()
31.     fmt.Printf("不使用Pool:%d s\n",t2-t1)
32.     fmt.Printf("使用Pool:%d s\n",t3-t2)
33. }

基于sync.Pool的上述特性,Pool的使用场景最多的是缓存,上面的示例简单测试了使用缓存和不使用缓存在执行时间上的差别。

第9行至第14行,定义了一个Pool里面存储的对象是byte切片,而且长度是1024,这一长度的选取主要是为了更为方便地测试效率。

第19行至第22行不使用缓存反复分配空间。

第25行至第29行使用前面定义的byteSlicePool作为缓存。

最后打印一下程序执行后的信息:


不使用Pool:3 s
使用Pool:149 s

可见Pool确实可以很好地提升程序执行效率。