同步之sync.Pool臨時對象池

同步之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=======

相關文章
相關標籤/搜索