Golang sync.Pool詳解

Golang中sync.Pool用來提高對象複用機率,減少gc的壓力,減少內存分配,它是線程安全的,常用來存儲並複用臨時對象。

原理

爲了減小併發中鎖的競爭,sync.pool爲每個P(對象cpu線程)分配一個子池子poolLocal,每個poolLocal有private對象和shared共享列表對象,private對象只有對應的P可訪問,無需加鎖, shared共享列表對象可被其它P共享,需要加鎖。

 

Pool結構體

type Pool struct {
	noCopy noCopy   //該對象不能被copy使用

	local     unsafe.Pointer // [p]poolLocal,固定長度
	localSize uintptr        //本地緩衝池poolLocal的數量

	New func() interface{}  //用戶自定義的用於生成對象的方法
}

 

Get函數

作用:從Pool中獲取一個對象,如果獲取不到並且New函數不爲空,則通過New創建一個對象並返回。否則返回nil

func (p *Pool) Get() interface{} {
	if race.Enabled {
		race.Disable()
	}
	//獲取當前線程的poolLocal
	l := p.pin()
	//如果private對象不爲空則直接返回,並將其置爲nil
	x := l.private
	l.private = nil
	runtime_procUnpin()
	if x == nil {
	    //private不存在則加鎖從shared列表中拿
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
		}
		l.Unlock()
		//如果shared對象列表依然沒有的話,則從其它P的poolLocal獲取
		if x == nil {
			x = p.getSlow()
		}
	}
	if race.Enabled {
		race.Enable()
		if x != nil {
			race.Acquire(poolRaceAddr(x))
		}
	}
	// 如果存在New func回調函數,則執行
	if x == nil && p.New != nil {
		x = p.New()
	}
	return x
}

 

Put()函數

作用:將對象x放入到Pool中,以便複用該對象

func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	if race.Enabled {
		if fastrand()%4 == 0 {
			// Randomly drop x on floor.
			return
		}
		race.ReleaseMerge(poolRaceAddr(x))
		race.Disable()
	}
	//如果當前poolLocal的private對象爲nil,則直接賦值
	l := p.pin()
	if l.private == nil {
		l.private = x
		x = nil
	}
	runtime_procUnpin()
	//否則加到當前poolLocal的shared列表中
	if x != nil {
		l.Lock()
		l.shared = append(l.shared, x)
		l.Unlock()
	}
	if race.Enabled {
		race.Enable()
	}
}

 

poolCleanup回收對象

sync.Pool會在gc時回收pool中的對象

func init() {
    runtime_registerPoolCleanup(poolCleanup)
}

func indexLocal(l unsafe.Pointer, i int) *poolLocal {
    lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
    return (*poolLocal)(lp)
}

// Implemented in runtime.
func runtime_registerPoolCleanup(cleanup func())
func runtime_procPin() int
func runtime_procUnpin()

func poolCleanup() {
    // This function is called with the world stopped, at the beginning of a garbage collection.
    // It must not allocate and probably should not call any runtime functions.
    // Defensively zero out everything, 2 reasons:
    // 1. To prevent false retention of whole Pools.
    // 2. If GC happens while a goroutine works with l.shared in Put/Get,
    //    it will retain whole Pool. So next cycle memory consumption would be doubled.
    for i, p := range allPools {
        allPools[i] = nil
        for i := 0; i < int(p.localSize); i++ {
            l := indexLocal(p.local, i)
            l.private = nil
            for j := range l.shared {
                l.shared[j] = nil
            }
            l.shared = nil
        }
        p.local = nil
        p.localSize = 0
    }
    allPools = []*Pool{}
}

 

 

感謝:http://www.mckee.cn/golang/pkg/syncpool

https://segmentfault.com/a/1190000019973632?utm_source=tag-newest

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