美文网首页
别再用 sync.Pool 了!Go 对象池的正确打开方式

别再用 sync.Pool 了!Go 对象池的正确打开方式

作者: 可乐他爸 | 来源:发表于2025-02-05 22:56 被阅读0次

什么是对象池?

对象池是一种设计模式,它维护一组已经创建好的对象,当需要使用对象时,直接从对象池中获取,使用完毕后再放回对象池,而不是频繁地创建和销毁对象。 这样可以显著减少 GC 的压力,提高程序的性能。

为什么不用 sync.Pool

sync.Pool 是 Go 标准库提供的对象池实现,但它有一些限制:

  • GC 不确定性: sync.Pool 中的对象可能会被 GC 回收,导致每次获取对象都需要重新创建,失去了对象池的意义。
  • 适用场景有限: sync.Pool 更适合于临时对象的复用,对于需要长期存在的对象,效果不佳。
  • 控制力不足: 无法精确控制对象池的大小和对象的生命周期。

因此,在某些场景下,我们需要自定义对象池,以获得更高的性能和控制力。

手撸对象池:原理与实现

下面,我们就来手撸一个简单的对象池,并分析其原理。

1. 定义对象池结构体

package main

import (
    "errors"
    "fmt"
    "sync"
    "time"
)

type Pool struct {
    objects chan interface{}   // 使用 channel 存储对象
    factory func() interface{} // 创建对象的工厂函数
    mu      sync.Mutex         // 保护对象池
}

var g_index int = 0

func NewPool(size int, factory func() interface{}) *Pool {
    if size <= 0 {
        panic("对象池大小必须大于 0")
    }
    pool := make(chan interface{}, size)
    for i := 0; i < size; i++ {
        pool <- factory() // 预先创建对象并放入对象池
    }
    return &Pool{
        objects: pool,
        factory: factory,
    }
}

func (p *Pool) Get() interface{} {
    select {
    case obj := <-p.objects:
        return obj // 从对象池中获取对象
    default:
        // 对象池为空,创建新对象
        fmt.Println("create new object")
        p.mu.Lock()
        defer p.mu.Unlock()
        return p.factory()
    }
}

func (p *Pool) Put(obj interface{}) error {
    select {
    case p.objects <- obj: // 对象放回对象池
        return nil
    default:
        // 对象池已满,丢弃对象
        obj2 := obj.(*MyObject)
        fmt.Println("pool is full, discard object", obj2.index)
        obj = nil
        return errors.New("pool is full")
    }
}

func (p *Pool) Len() int {
    return len(p.objects)
}

type MyObject struct {
    Data  string
    index int
}

func main() {
    // 创建对象工厂
    objectFactory := func() interface{} {
        g_index += 1
        return &MyObject{Data: "Initial Data", index: g_index}
    }

    // 创建对象池,大小为 10
    pool := NewPool(10, objectFactory)

    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(idx int) {
            fmt.Println("pool len:", pool.Len())
            obj := pool.Get().(*MyObject)
            defer func() {
                wg.Done()
                pool.Put(obj)
            }()
            fmt.Println("from :", obj.Data, obj.index, idx)
            time.Sleep(time.Millisecond * 2) // 模拟一些工作
        }(i)
    }
    wg.Wait()
}

代码解释:

  • ObjectPool 结构体包含一个 pool channel,用于存储对象。
  • factory 是一个函数,用于创建新的对象。
  • NewObjectPool 函数用于创建对象池,并预先创建指定数量的对象放入对象池。
  • Get 函数用于从对象池中获取对象。如果对象池为空,则调用 factory 创建新的对象。
  • Put 函数用于将对象放回对象池。如果对象池已满,则丢弃对象。
  • 定义了一个 MyObject 结构体,作为对象池中存储的对象类型。
  • 创建一个 objectFactory 函数,用于创建 MyObject 对象。
  • 创建一个大小为 10 的对象池,并传入 objectFactory 函数。
  • 从对象池中获取对象,修改对象的数据,然后将对象放回对象池。
  • 再次从对象池中获取对象,可以看到对象的数据已经被修改,说明对象被成功复用。

总结

通过手撸对象池,我们不仅可以更好地理解对象池的原理,还可以根据实际需求定制对象池,以获得更高的性能和控制力。 在需要频繁创建和销毁对象的场景下,使用对象池可以显著提高程序的性能,告别 GC 噩梦!

感谢阅读!如果你觉得这篇文章对你有帮助,请分享给你的朋友们,让更多的人一起学习Go语言

相关文章

网友评论

      本文标题:别再用 sync.Pool 了!Go 对象池的正确打开方式

      本文链接:https://www.haomeiwen.com/subject/fcogujtx.html