Golang對象池實現

一.sync.Pool定義

       我們通常用golang來構建高併發場景下的應用,但是由於golang內建的GC機制會影響應用的性能,爲了減少GC,golang提供了對象重用的機制,也就是sync.Pool對象池。 sync.Pool是可伸縮的,併發安全的。其大小僅受限於內存的大小,可以被看作是一個存放可重用對象的值的容器。 設計的目的是存放已經分配的但是暫時不用的對象,在需要用到的時候直接從pool中取。

       官方的解釋:臨時對象池是一些可以分別存儲和取出的臨時對象,池中的對象會在沒有任何通知的情況下移出(釋放或重新使用)。pool在多協程的環境下是安全的,在fmt包中有一個使用pool的例子,它維護了一個動態大小的輸出buffer。另外,一些短生命週期的對象不適合使用pool來維護,這種情況使用go自己的free list更高效。

二.sync.Pool的實現

2.1使對象池高效

       爲了使多個goroutine操作同一個pool做到高效,sync.pool爲每一個p都分配了一個子池。當執行get或者put操作時,會對當前goroutine掛載的子池操作。每個子池都有一個私有對象和共享列表對象,私有對象只有對應的p能夠訪問,因爲同一個p同一時間只能操作執行一個goroutine,因此對私有對象的操作不需要加鎖;但共享列表是和其他P分享的,因此操作是需要加鎖的。

獲取對象的過程:

  • 固定某個P,嘗試從私有對象中獲取, 如果是私有對象則返回該對象,並把私有對象賦空。
  • 如果私有對象是空的,需要加鎖,從當前固定的p的共享池中獲取-並從該共享隊列中刪除這個對象。
  • 如果當前的子池都是空的,嘗試去其他P的子池的共享列表偷取一個,如果用戶沒有註冊New函數則返回nil。

歸還對象的過程:

  • 固定到某個p,如果私有對象爲空則放到私有對象。
  • 如果私有對象不爲空,加鎖,加入到該P子池的共享列表中。

2.2 對象池的詳細實現

2.2.1 對象池結構

type Pool struct {
	noCopy noCopy            //防止copy

	local     unsafe.Pointer //本地p緩存池指針
	localSize uintptr        //本地p緩存池大小

	//當池中沒有對象時,會調用New函數調用一個對象
	New func() interface{}
}

2.2.2 獲取對象池中的對象

 

func (p *Pool) Get() interface{} {
	if race.Enabled {
		race.Disable()
	}
        //獲取本地的poolLocal對象
	l := p.pin()

        //先獲取private池中的私有變量
	x := l.private
	l.private = nil
	runtime_procUnpin()
	if x == nil {
                //查找本地的共享池,因爲本地的共享池可能被其他p訪問,所以要加鎖
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
                        //如果本地共享池有對象,取走最後一個
			x = l.shared[last]
			l.shared = l.shared[:last]
		}
		l.Unlock()
                //查找其他p的共享池
		if x == nil {
			x = p.getSlow()
		}
	}
	if race.Enabled {
		race.Enable()
		if x != nil {
			race.Acquire(poolRaceAddr(x))
		}
	}
        //未找到其他可用元素,則調用New生成
	if x == nil && p.New != nil {
		x = p.New()
	}
	return x
}

從共享池中獲取可用元素:

func (p *Pool) getSlow() (x interface{}) {
	// See the comment in pin regarding ordering of the loads.
	size := atomic.LoadUintptr(&p.localSize) // load-acquire
	local := p.local                         // load-consume
	// Try to steal one element from other procs.
	pid := runtime_procPin()
	runtime_procUnpin()
	for i := 0; i < int(size); i++ {
		l := indexLocal(local, (pid+i+1)%int(size))
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
			l.Unlock()
			break
		}
		l.Unlock()
	}
	return x
}

2.2.3 歸還對象池中的對象

func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	if race.Enabled {
		if fastrand()%4 == 0 {
			//1/4的概率會把該元素扔掉
			return
		}
		race.ReleaseMerge(poolRaceAddr(x))
		race.Disable()
	}
	l := p.pin()
	if l.private == nil {
                //賦值給私有變量
		l.private = x
		x = nil
	}
	runtime_procUnpin()
	if x != nil {
                //訪問共享池加鎖
		l.Lock()
		l.shared = append(l.shared, x)
		l.Unlock()
	}
	if race.Enabled {
		race.Enable()
	}
}

三.sync.Pool 使用

// 一個[]byte的對象池,每個對象爲一個[]byte
var bytePool = sync.Pool{
  New: func() interface{} {
    b := make([]byte, 512)
    return &b
  },
}

func main() {
  a := time.Now().Unix()
  // 不使用對象池
  for i := 0; i < 1000000000; i++{
    obj := make([]byte,512)
    _ = obj
  }
  b := time.Now().Unix()
  // 使用對象池
  for i := 0; i < 1000000000; i++{
    obj := bytePool.Get().(*[]byte)
    _ = obj
    bytePool.Put(obj)
  }
  c := time.Now().Unix()
  fmt.Println("without pool ", b - a, "s")
  fmt.Println("with    pool ", c - b, "s")
}

// without pool  17 s
// with    pool  12 s

四.sync.Pool的使用場景

       sync.Pool的get方法不會對獲取到的對象做任何的保證,因爲放入的本地子池中的值可能在任何是由被刪除,而且不會通知調用者。放入共享池的值有可能被其他的goroutine偷走。隨意臨時對象池適合存儲一些臨時數據,不適合用來存儲數據庫連接等持久化存儲的對象。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章