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

2025-02-05  本文已影响0人  可乐他爸

什么是对象池?

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

为什么不用 sync.Pool

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

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

手撸对象池:原理与实现

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

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()
}

代码解释:

总结

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

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

上一篇 下一篇

猜你喜欢

热点阅读