sync Pool 代码阅读

Pool结构体

type Pool struct {
	noCopy noCopy

	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{}
}

poolLocal:
每个p有不一样的对象,其中private只能提供给p使用,shared可以被其他p进行steal,但是需要加锁

// Local per-P Pool appendix.
type poolLocalInternal struct {
	private interface{}   // Can be used only by the respective P.
	shared  []interface{} // Can be used by any P.
	Mutex                 // Protects shared.
}

Get方法

func (p *Pool) Get() interface{} {
	...
	l := p.pin()  // 选到p对应的poolLocal
	x := l.private
	l.private = nil
	runtime_procUnpin()
	if x == nil { // private 没有
		l.Lock() // 加锁
		last := len(l.shared) - 1
		if last >= 0 { // 尝试从自己共享的对象取一个
			x = l.shared[last]
			l.shared = l.shared[:last]
		}
		l.Unlock()
		if x == nil {  // 没办法只能去其他p去steal
			x = p.getSlow()
		}
	}
	...
	if x == nil && p.New != nil {
		x = p.New() // 创建一个
	}
	return x
}

get方法流程:

  1. p从自己私有对象获取(无锁)
  2. p从自己共享对象获取(局部锁,一个p)
  3. p从其他p偷取共享对象(循环上锁解锁,所以slow)
  4. 没有在生成

Put方法

// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	...
	l := p.pin() // 获取p的poollocal对象
	if l.private == nil { // 优先设置私有
		l.private = x
		x = nil
	}
	runtime_procUnpin()
	if x != nil {
		l.Lock()
		l.shared = append(l.shared, x) // 追加到自己共享区中
		l.Unlock()
	}
	...
}

Put方法流程:

  1. 尝试加入私有对象中
  2. 追加到共享对象中

Pin和getSlow

// pin pins the current goroutine to P, disables preemption and returns poolLocal pool for the P.
// Caller must call runtime_procUnpin() when done with the pool.
func (p *Pool) pin() *poolLocal {
	pid := runtime_procPin()
	// In pinSlow we store to localSize and then to local, here we load in opposite order.
	// Since we've disabled preemption, GC cannot happen in between.
	// Thus here we must observe local at least as large localSize.
	// We can observe a newer/larger local, it is fine (we must observe its zero-initialized-ness).
	s := atomic.LoadUintptr(&p.localSize) // load-acquire 设置保证GC无法发生
	l := p.local                          // load-consume
	if uintptr(pid) < s {  // p已经存在
		return indexLocal(l, pid)
	}
	return p.pinSlow() // p是新的,生成poollocal结构,并且注册在全局对象allPools上,GC时会扫描。
}

func (p *Pool) pinSlow() *poolLocal {
	// Retry under the mutex.
	// Can not lock the mutex while pinned.
	runtime_procUnpin()
	allPoolsMu.Lock()
	defer allPoolsMu.Unlock()
	pid := runtime_procPin()
	// poolCleanup won't be called while we are pinned.
	s := p.localSize
	l := p.local
	if uintptr(pid) < s {
		return indexLocal(l, pid)
	}
	if p.local == nil {
		allPools = append(allPools, p) // 注册
	}
	// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.
	size := runtime.GOMAXPROCS(0)
	local := make([]poolLocal, size)
	atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
	atomic.StoreUintptr(&p.localSize, uintptr(size))         // store-release
	return &local[pid]
}

getslow:

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++ { // local全局pid到poollocal的对象
		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
}

GC

GC会把所有poo清空 。。。注意千万别在意对象的生命周期。。

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{}
}

var (
	allPoolsMu Mutex
	allPools   []*Pool
)

func init() {
	runtime_registerPoolCleanup(poolCleanup)
}

总结

  1. pool 使用需要谨慎,优先确认是否有必要pool存储临时对象。使用前应该了解benchmark评估下与直接生成对象的性能开销对比。优先建议通过堆内存逃逸检查下,如果能把临时对象控制在栈中最好。
  2. 注意千万别在意对象的生命周期,有类似场景应该避开。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Golang 的内存池使用起来很简单,只需要使用 sync.Pool 包中的 Get 和 Put 方法,就可以在内存池中请求和释放内存块: // 创建一个内存池 pool := &sync.Pool { New: func() interface{} { b := make([]byte, 1024) return &b }, }// 从内存池中请求内存块 buf := pool.Get().(*[]byte)// 使用完毕后,将内存块释放回内存池 pool.Put(buf) ### 回答2: 使用 golang 的内存池可提高性能,减少内存分配的开销,并减轻垃圾回收的负担。下面是一个简单的示例代码: 首先,我们需要导入 "sync" 包用于同步操作,以及 "github.com/oleiade/lane" 包用于实现队列(FIFO)。 ``` import ( "sync" "github.com/oleiade/lane" ) // 定义一个内存池结构 type MemoryPool struct { pool *lane.Queue // 使用队列实现内存池 maxSize int // 内存池的最大容量 lock sync.Mutex // 用于同步操作的锁 } // 初始化内存池 func NewMemoryPool(maxSize int) *MemoryPool { return &MemoryPool{ pool: lane.NewQueue(), // 初始化队列 maxSize: maxSize, // 设置最大容量 } } // 获取一个对象 func (p *MemoryPool) Get() interface{} { p.lock.Lock() // 加锁 defer p.lock.Unlock() if p.pool.Size() > 0 { return p.pool.Dequeue() // 如果池中有可用对象,则出队并返回 } return nil // 池中无可用对象时返回 nil } // 释放一个对象 func (p *MemoryPool) Put(obj interface{}) { p.lock.Lock() // 加锁 defer p.lock.Unlock() if p.pool.Size() < p.maxSize { p.pool.Enqueue(obj) // 如果池未满,则将对象入队 } } ``` 这份代码中,我们创建了一个内存池结构 `MemoryPool`,其中包含一个 `lane.Queue` 类型的队列 `pool`,用于存储已经分配的对象。`maxSize` 属性表示内存池的最大容量。 在 `Get()` 方法中,首先通过加锁操作来保证并发安全。如果内存池中有可用对象,就将对象从队列中出队并返回;否则返回 `nil`。 在 `Put()` 方法中,也通过加锁来保证并发安全。如果内存池未满,就将对象入队。 这样,我们就实现了一个简单的 golang 内存池。在实际应用中,可以根据具体需求对内存池进行优化和扩展。 ### 回答3: Golang内存池是一种可以提供重复使用的内存资源的机制,可以有效地减少内存分配和回收的开销。以下是一个简单的使用Golang内存池的代码示例: ```go package main import ( "fmt" "sync" ) var pool = sync.Pool{ New: func() interface{} { return make([]byte, 1024) // 初始化一个长度为1024的字节切片 }, } func main() { // 从内存池中获取一个字节切片 data := pool.Get().([]byte) // 断言类型为[]byte // 使用字节切片进行数据操作 copy(data, []byte("Hello, world!")) // 打印字节切片中的数据 fmt.Println(string(data)) // 将字节切片重新放回内存池 pool.Put(data) // 从内存池中获取一个新的字节切片 newData := pool.Get().([]byte) // 此时newData的内容与之前的data相同,可以继续使用 fmt.Println(string(newData)) // 清空内存池 pool.New = nil } ``` 在以上示例中,我们首先创建了一个sync.Pool对象,其New字段是一个匿名函数,用于创建一个长度为1024的字节切片,并将其作为默认的新对象。 接下来,在main函数中,我们通过pool.Get()方法从内存池中获取一个字节切片,并进行数据操作。当我们完成后,通过pool.Put()方法将字节切片重新放回内存池。 对于下一次需要新的字节切片时,我们可以再次调用pool.Get()方法,此时会从内存池中获取之前放回的字节切片,因此可以继续使用。 最后,我们可以通过将pool.New字段设置为nil来清空内存池,使其在下一次pool.Get()时重新生成新的对象。 使用内存池可以避免频繁的内存分配和回收操作,提高程序的性能。不过需要注意的是,内存池中的对象并不是永久存在的,可能会在不同的时间点被回收,因此必要时需要考虑重新分配内存。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值