Golang基本同步原语sync.Pool

sync.Pool 是 Go 标准库 sync 包中的一个结构,用于管理一组可重用的对象池。通过复用对象,减少内存分配和垃圾回收的开销,从而提升程序的性能和效率。以下是对 sync.Pool 的详细讲解,包括其使用案例和使用场景。

sync.Pool 的大小是可伸缩的,高负载时会动态扩容,存放在池中的对象如果不活跃了会被自动清理。


1. sync.Pool 的功能和作用

sync.Pool 主要用于在多个 goroutine 之间高效地共享和重用临时对象。它通过保持一个池子,存储已经分配但当前不使用的对象。当需要新的对象时,首先从池子中获取,如果池子中没有可用的对象,则创建新的对象。使用完成后,将对象归还池子,以便后续复用。

sync.Pool 的主要优势在于减少内存分配和垃圾回收的开销,尤其在需要频繁创建和释放临时对象的场景下,能够显著提升程序的性能。

2.sync.Pool核心方法

sync.Pool 提供了以下几个核心方法:

New()

pool := &sync.Pool{
    New: func() interface{} {
        // 返回一个新的对象
        return new(Object)
    },
}

  • 作用:初始化一个新的 sync.Pool 实例,New 是一个工厂函数,用于创建池子中对象的工厂方法。
  • 场景:当池子中没有可用对象时,会调用 New 方法创建新的对象。

Get()

obj := pool.Get()

  • 作用:从池子中获取一个可用的对象。如果池子中有对象,直接返回;如果没有,调用 New 方法创建新的对象。
  • 返回值:返回一个 interface{} 类型的对象,需要进行类型断言。

Put()

pool.Put(obj)
  • 作用:将一个对象归还到池子中,供后续复用。
  • 注意:归还的对象应该处于初始状态,避免残留数据导致潜在的数据竞争或不一致问题。

Close()

pool.Close()

  • 作用:关闭池子,防止新的 GetPut 操作。关闭后,已经获取的对象可以继续使用,但池子不会接受新的归还。
  • 场景:在程序不再需要池子时,调用 Close() 方法进行清理。

使用场景

sync.Pool 适用于以下场景:

  1. 频繁创建和释放临时对象
    • 例如,处理大量的网络请求时,使用池子管理临时的缓冲区或上下文对象,减少内存分配和垃圾回收的开销。
  2. 减少内存分配开销
    • 对于需要频繁创建和销毁的对象,使用池子可以复用已有对象,减少内存分配和回收的次数,提升性能。
  3. 提升并发性能
    • 在多个 goroutine 之间共享对象时,池子提供了一种高效的方式,减少了竞争和内存分配的开销。

image-20250129012610286


package main_test

import (
	"bytes"
	"sync"
	"testing"
)

var bufferPool = sync.Pool{New: func() any {
	return bytes.NewBuffer(make([]byte, 0, 1024))
}}
var data = make([]byte, 100000)

func BenchmarkBufferWithPool(b *testing.B) {
	for n := 0; n < b.N; n++ {
		buf := bufferPool.Get().(*bytes.Buffer)
		buf.Write(data)
		buf.Reset()
		bufferPool.Put(buf)
	}
}

func BenchmarkBuffer(b *testing.B) {
	for n := 0; n < b.N; n++ {
		var buf *bytes.Buffer = bytes.NewBuffer(nil)
		buf.Write(data)
	}
}

image-20250129014537187

image-20250129014552247

这个例子创建了一个 bytes.Buffer 对象池,而且每次只执行一个简单的 Write 操作,存粹的内存搬运工,耗时几乎可以忽略。而内存分配和回收的耗时占比较多,因此对程序整体的性能影响更大。