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确实可以很好地提升程序执行效率。