golang类型安全泛型对象池管理插件库xpool的使用

golang类型安全泛型对象池管理插件库xpool的使用

xpool是一个用户友好、类型安全的sync.Pool版本,受到xpool的启发。

定义

这个包定义了一个接口Pool[T any]

// Pool是一个类型安全的对象池接口
// 为了方便,*sync.Pool是一个Pool[any]
type Pool[T any] interface {
    // 从对象池获取一个项目
    // 如果需要,会创建另一个对象
    Get() T

    // 将对象放回池中
    // 在放回sync pool之前可能会重置对象
    Put(object T)
}

这样*sync.Pool就是一个Pool[any]

使用

假设你需要一个由bytes.Buffer实现的io.ReadWriter接口池。你不再需要从interface{}any类型转换,只需:

pool := xpool.New(func() io.ReadWriter {
    return new(bytes.Buffer)
})

rw := pool.Get()
defer pool.Put(rw)

// 现在你可以使用一个新的io.ReadWrite实例

而不是使用纯Go的方式:

pool := &sync.Pool{
    New: func() any {
        return new(bytes.Buffer)
    },
}

rw, _ := pool.Get().(io.ReadWriter)
defer pool.Put(rw)

// 现在你可以使用一个新的io.ReadWrite实例

对象池非常适合那些创建简单的对象,比如那些没有参数的构造函数。如果我们需要指定参数来创建一个对象,那么每个参数组合可能会创建一个不同的对象,它们不容易从对象池中使用。

有两种可能的方法:

  1. 映射所有可能的参数并为每种组合创建一个对象池。
  2. 创建可以轻松创建的单子对象,并且可以通过某些方法设置特定状态。

第二种方法我们称之为"可重置对象"。

处理单子可重置对象

对象池非常适合无状态对象,但在处理单子对象时,我们需要格外小心对象状态。幸运的是,我们有一些对象可以在重用前轻松重置状态。

一些类别的对象如hash.Hash和bytes.Buffer,我们可以调用Reset()方法将对象返回到初始状态。其他如bytes.Reader和gzip.Writer对Reset(state S)有特殊含义,以便可以重用同一个对象而不是创建一个新对象。

我们定义两种形式的Reset:

Niladic接口,其中Reset()不接受参数(例如hash.Hash的情况),在将对象放回池之前执行。

// Resetter接口
type Resetter interface {
    Reset()
}

Monadic接口,其中Reset(S)接受一个参数(例如gzip.Writer的情况),当我们从池中获取对象并使用类型S的值初始化时执行,并在将对象放回池之前重置为S的零值。

// Resetter接口
type Resetter[S any] interface {
    Reset(state S)
}

Monadic重置器由xpool/monadic包处理。

重要提示:你可能不想暴露带有Reset方法的对象,xpool不会确保类型TResetter[S],除非你使用NewWithResetter构造函数。

示例

在将对象放回池之前调用Reset(),在xpool包中:

var pool xpool.Pool[hash.Hash] = xpool.NewWithResetter(func() hash.Hash {
    return sha256.New()
})

hasher := pool.Get()   // 获取一个新的hash.Hash接口
defer pool.Put(hasher) // 在放回sync pool之前用nil重置它

_, _ = hasher.Write(p)

value := hasher.Sum(nil)

在获取实例时使用某个值调用Reset(v),并在将对象放回池之前调用Reset(<零值>),在xpool/monadic包中:

// 这个构造函数无法推断类型S,所以你应该明确指定!
var pool monadic.Pool[[]byte,*bytes.Reader] = monadic.New[[]byte](
    func() *bytes.Reader {
        return bytes.NewReader(nil)
    },
)

reader := pool.Get([]byte(`payload`)) // 用payload重置bytes.Reader
defer pool.Put(reader)                // 用nil重置bytes.Reader

content, err := io.ReadAll(reader)

自定义重置器

可以设置一个自定义的线程安全重置器,而不是只调用Reset()Reset(v),通过自定义重置器,而不是使用默认的。

在xpool包中:

//除了日志,两个调用是等效的

pool:= xpool.NewWithCustomResetter(sha256.New, 
    func(h hash.Hash) {
        h.Reset()

        log.Println("just reset the hash.Hash")
    },
),

// 默认的重置器尝试调用`Reset()`方法
pool:=  xpool.NewWithDefaultResetter(sha256.New),

在xpool/monadic包中:

// 除了日志,两个调用是等效的
    
// monadic池默认会尝试调用`Reset([]byte)`方法
pool:= monadic.New[[]byte](func() *bytes.Reader {
    return bytes.NewReader(nil)
})

// monadic池会尝试调用特定的重置器回调
pool:= monadic.NewWithCustomResetter(func() *bytes.Reader {
    return bytes.NewReader(nil)
}, func(object *bytes.Reader, state []byte) {
    object.Reset(state)

    log.Println("just reset the *bytes.Buffer")
})

你可以使用自定义重置器来处理更复杂的Reset类型。例如,flate.NewReader返回一个io.ReadCloser,它也实现了flate.Resetter,支持不同类型的Reset(),它期望两个参数并返回一个错误。

如果我们能丢弃错误并将第二个参数设置为nil这样的常量值,我们可以:

// 可以从重置器推断类型
poolReader := monadic.NewWithCustomResetter(func() io.ReadCloser {
    return flate.NewReader(nil)
}, func(object io.ReadCloser, state io.Reader) {
    if resetter, ok := any(object).(flate.Resetter); ok {
        _ = resetter.Reset(state, nil)
    }
})

另一种方法是创建一个对象来保存不同的参数,如下例所示:

type flateResetterArgs struct {
    r    io.Reader
    dict []byte
}
// 可以从重置器推断类型S
poolReader := monadic.NewWithCustomResetter(func() io.ReadCloser {
    return flate.NewReader(nil)
}, func(object io.ReadCloser, state *flateResetterArgs) {
    if resetter, ok := any(object).(flate.Resetter); ok {
        _ = resetter.Reset(state.r, state.dict)
    }
})

自定义重置器可以做的不仅仅是设置对象的状态,它们可以用于日志记录、跟踪和提取指标。

重要提示

在xpool中重置器是可选的,而在xpool/monadic中是强制性的。如果你不想在monadic xpool中有重置器,请创建一个常规的xpool.Pool


更多关于golang类型安全泛型对象池管理插件库xpool的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang类型安全泛型对象池管理插件库xpool的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


xpool - Golang类型安全泛型对象池管理库

xpool 是一个基于泛型的 Golang 对象池管理库,提供了类型安全的对象复用功能,可以有效减少内存分配和GC压力。下面我将详细介绍其使用方法。

基本特性

  1. 完全类型安全 - 基于Go 1.18+泛型实现
  2. 高性能 - 使用sync.Pool作为底层实现
  3. 简单易用 - 提供简洁的API接口
  4. 可配置 - 支持自定义对象创建和销毁逻辑

安装

go get github.com/xxjwxc/xpool

基本使用示例

package main

import (
	"fmt"
	"github.com/xxjwxc/xpool"
)

type MyObject struct {
	ID   int
	Name string
}

func main() {
	// 创建对象池
	pool := xpool.NewPool[MyObject](
		func() *MyObject { // 创建函数
			return &MyObject{}
		},
		func(obj *MyObject) { // 重置函数
			obj.ID = 0
			obj.Name = ""
		},
	)

	// 从池中获取对象
	obj := pool.Get()
	obj.ID = 1
	obj.Name = "test"

	// 使用完毕后放回池中
	pool.Put(obj)

	// 再次获取
	obj2 := pool.Get()
	fmt.Printf("obj2: %+v\n", obj2) // 输出: obj2: &{ID:0 Name:}
}

高级配置

自定义池大小

pool := xpool.NewPoolWithSize[MyObject](
	func() *MyObject {
		return &MyObject{}
	},
	func(obj *MyObject) {
		obj.ID = 0
		obj.Name = ""
	},
	100, // 初始大小
	200, // 最大大小
)

带错误处理的对象池

pool := xpool.NewPoolWithError[MyObject](
	func() (*MyObject, error) {
		if time.Now().Unix()%2 == 0 {
			return &MyObject{}, nil
		}
		return nil, fmt.Errorf("random error")
	},
	func(obj *MyObject) {
		obj.ID = 0
		obj.Name = ""
	},
)

obj, err := pool.TryGet()
if err != nil {
	fmt.Println("get object failed:", err)
} else {
	// 使用obj
	pool.Put(obj)
}

性能优化建议

  1. 合理设置池大小:根据实际场景调整初始和最大大小
  2. 复用大对象:特别适合复用大内存对象或复杂结构体
  3. 避免频繁Put/Get:批量操作更高效
  4. 重置对象状态:确保Put前重置对象状态

注意事项

  1. 对象池不适合所有场景,对于简单小对象可能反而降低性能
  2. 确保放回池中的对象是干净的,避免数据污染
  3. 并发安全,但单个对象不保证并发安全

实际应用示例

// 数据库连接池模拟
type DBConnection struct {
	conn *sql.DB
	busy bool
}

func main() {
	pool := xpool.NewPool[DBConnection](
		func() *DBConnection {
			db, _ := sql.Open("mysql", "user:pass@/dbname")
			return &DBConnection{conn: db}
		},
		func(conn *DBConnection) {
			conn.busy = false
		},
	)

	// 处理HTTP请求
	http.HandleFunc("/query", func(w http.ResponseWriter, r *http.Request) {
		conn := pool.Get()
		defer pool.Put(conn)
		
		conn.busy = true
		// 执行查询...
		rows, _ := conn.conn.Query("SELECT * FROM users")
		// 处理结果...
	})

	http.ListenAndServe(":8080", nil)
}

xpool 通过泛型提供了类型安全的对象池管理,相比标准库的 sync.Pool 提供了更丰富的功能和更好的类型安全性,适合在需要频繁创建销毁特定类型对象的场景中使用。

回到顶部