当前位置: 首页 > 面试题库 >

如何在Golang中实现内存池

尚嘉庆
2023-03-14
问题内容

我在Go中实现了HTTP服务器。

对于每个请求,我需要为一个特定的结构创建数百个对象,并且我有大约10个这样的结构。因此,按照Go实现完成请求后,将对其进行垃圾回收。

因此,对于每个请求,将分配和释放大量的内存。

相反,我想实现内存池以提高分配端以及GC端的性能。

在请求开始时,我将从池中取出并在请求处理后放回去

从池实施方面

  1. 如何分配和取消分配特定类型结构的内存?
  2. 如何跟踪此内存已分配而其他内存未分配的信息?

在内存分配和释放的情况下,还有其他提高性能的建议吗?


问题答案:

事先注意:

许多人建议sync.Pool临时
对象使用快速,良好的实现。但是请注意,sync.Pool这不能保证保留合并的对象。引用其文档:

池中存储的任何项目都 可以随时自动删除,恕不另行通知 。如果发生这种情况时,池中只有唯一的引用,则该项目可能会被释放。

因此,如果您不希望对象中的对象Pool被垃圾回收(这取决于您的情况,可能会导致更多分配),那么下面介绍的解决方案会更好,因为通道缓冲区中的值不会被垃圾回收。如果您的对象确实足够大以至于有足够的内存池,则将分摊池通道的开销。

此外,sync.Pool不允许您限制合并对象的数量,而下面提供的解决方案自然可以。

最简单的内存池“实现”是一个缓冲通道。

假设您想要一些大对象的内存池。创建一个缓冲的通道,其中包含指向此类昂贵对象的值的指针,并且在需要时,从池(通道)中接收一个。使用完后,将其放回池中(在通道上发送)。为了避免意外丢失对象(例如在发生紧急情况时),请defer在放回它们时使用声明。

让我们将其用作大对象的类型:

type BigObject struct {
    Id        int
    Something string
}

创建一个池是:

pool := make(chan *BigObject, 10)

池的大小就是通道缓冲区的大小。

用昂贵的对象的指针填充池(这是可选的,请参阅最后的注释):

for i := 0; i < cap(pool); i++ {
    bo := &BigObject{Id: i}
    pool <- bo
}

通过许多goroutines使用池:

wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        bo := <-pool
        defer func() { pool <- bo }()
        fmt.Println("Using", bo.Id)
        fmt.Println("Releasing", bo.Id)
    }()
}

wg.Wait()

在Go Playground上尝试一下。

请注意,如果所有“池”对象都在使用中,则此实现会阻塞。如果您不希望这样做,则可以select在所有正在使用的对象上强制创建新对象:

var bo *BigObject
select {
case bo = <-pool: // Try to get one from the pool
default: // All in use, create a new, temporary:
    bo = &BigObject{Id:-1}
}

在这种情况下,您无需将其放回池中。或者,如果池中有空间,则可以选择再次尝试将所有内容放回池中,而不会阻塞select

select {
case pool <- bo: // Try to put back into the pool
default: // Pool is full, will be garbage collected
}

笔记:

预先填充池是可选的。如果select用于尝试从池中获取值/将值放回池中,则池最初可能是空的。

您必须确保您不会在请求之间泄漏信息,例如,请确保您没有在共享对象中使用已设置并属于其他请求的字段和值。



 类似资料:
  • 问题内容: 从缓存还原后,我需要在磁盘上缓存resp并保持其类型为http.Response。有任何想法吗? 问题答案: 最简单的方法是使用httputil.DumpResponse和http.ReadResponse。 请参阅此处的示例。(您必须将代码复制到本地计算机上并在本地计算机上运行,​​因为Playground不允许I / O) 第一个将接收到的请求转储到内存中的[]字节中,然后可以将其

  • 问题内容: 我已经在“ Go编程语言”中读到“无论哈希表有多大,平均都可以使用恒定数量的键比较来检索给定的键”。我不确定这在内部实现方面意味着什么。这是否意味着它会搜索每个键,直到找到匹配项,或者内部使用某种类型的二进制(或其他)搜索算法? 例如,如果我有一个具有2,000个键的地图,那么它“平均”是否需要查看1,000才能找到匹配项?或者它只像二进制搜索那样只查看11(log2 n)? 谢谢,本

  • 问题内容: 我有一些C方面的经验,对golang完全陌生。 现在在golang slice中是array的引用,其中包含指向slice的数组len和slice的上限的指针,但是该slice也将分配在内存中,我想打印该内存的地址。但是无法做到这一点。 问题答案: http://golang.org/pkg/fmt/ 将打印地址。

  • 1)我想从缓存中获得数据,如果缓存中没有数据,那么应该从数据库中获取数据。 2)如果我点击/api/cacherefresh,控制器将刷新所有表。

  • 问题内容: 所有, 下面的代码来自“ Unix环境中的高级编程”,它创建一个新线程,并打印主线程和新线程的进程ID和线程ID。 在书中,它表示在linux中,此代码的输出将显示两个线程具有不同的进程ID,因为pthread使用轻量级进程来模拟线程。但是,当我在Ubuntu 12.04中运行此代码时,它具有内核3.2,并打印了相同的pid。 那么,新的Linux内核是否会更改pthread的内部实现

  • 我目前正在将我的数据库层(实体和DAO)从JavaEE迁移到Quakus(或者从wls迁移到OpenShift),并且不能完全理解一些细节: 目前我有目前的结构: 信息类注册时间。爪哇 我的测试类注册了TimeDaoTest,它使用TestDB在内存中执行以下操作: } 我一直潜伏在quarkus.io,读到以下内容: https://quarkus.io/guides/datasource ht