同步之sync.Pool临时对象池golang
当多个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. }
获取对象过程是:app
1)固定到某个P,尝试从私有对象获取,若是私有对象非空则返回该对象,并把私有对象置空;函数
2)若是私有对象是空的时候,就去当前子池的共享列表获取(须要加锁);ui
3)若是当前子池的共享列表也是空的,那么就尝试去其余P的子池的共享列表偷取一个(须要加锁);.net
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=======