同步Map可能导致内存和goroutine的增加

qv7cva1a  于 2022-12-16  发布在  Go
关注(0)|答案(2)|浏览(113)

嗨,这里是代码,我使util称为收集器

import (
    "context"
    "errors"
    "sync"
    "time"
)

type Collector struct {
    keyValMap *sync.Map
}

func (c *Collector) LoadOrWait(key any) (retValue any, availability int, err error) {
    value, status := c.getStatusAndValue(key)

    switch status {
    case 0:
        return nil, 0, nil
    case 1:
        return value, 1, nil
    case 2:
        ctxWithTimeout, _ := context.WithTimeout(context.Background(), 5 * time.Second)
        for {
            select {
            case <-ctxWithTimeout.Done():
                return nil, 0, errRequestTimeout
            default:
                value, resourceStatus := c.getStatusAndValue(key)
                if resourceStatus == 1 {
                    return value, 1, nil
                }
                time.Sleep(50 * time.Millisecond)
            }
        }
    }

    return nil, 0, errRequestTimeout
}

// Store ...
func (c *Collector) Store(key any, value any) {
    c.keyValMap.Store(key, value)
}

func (c *Collector) getStatusAndValue(key any) (retValue any, availability int) {
    var empty any
    result, loaded := c.keyValMap.LoadOrStore(key, empty)

    if loaded && result != empty {
        return result, 1
    }

    if loaded && result == empty {
        return empty, 2
    }

    return nil, 0
}

所以这个工具的目的是作为一个缓存,类似的值只被加载一次,但被读取很多次。然而,当一个收集器的对象被传递给多个goroutine时,我面临着goroutine和ram使用的增加,每当多个goroutine试图使用收集器缓存时。有人能解释一下同步Map的使用是否正确吗?如果是的话,那么是什么原因导致了大量的goroutine/较高RAM使用率

xggvc2p6

xggvc2p61#

当然,由于没有调用新创建的ctxWithTimeout上下文的cancel函数,您可能会遇到内存泄漏。为了解决这个问题,请将该行更改为:

ctxWithTimeout, cancelFunc := context.WithTimeout(context.Background(), requestTimeout)
defer cancelFunc()

多亏了这个,你总是可以确保在上下文过期后清理所有分配的资源,这应该可以解决泄漏的问题。
关于sync.Map的用法我觉得不错。
让我知道这是否解决了您的问题,或者如果有其他问题需要解决,谢谢!

qxsslcnc

qxsslcnc2#

您在读取器端显示代码,但不显示执行请求(并调用.Store(key, value))的代码。
使用您显示的代码:

  • 第一个试图访问给定键的goroutine会把你的empty值保存在map中(当执行c.keyValMap.LoadOrStore(key, empty)时),
  • 所以所有的goroutine在查询同一个键之后都会进入“带超时的查询”循环--即使实际运行请求并将结果存储该高速缓存中的操作没有执行。

[更新后]
在资源消耗方面,收集器的代码本身似乎是可以的:我在这段代码中并没有看到死锁或者goroutine的乘法。
您可能应该查看代码中的其他地方。
同样,如果这个结构只增长不收缩,它肯定会消耗更多的内存,一定要审计你的程序,评估有多少不同的键可以在你的缓存中共存,以及缓存的值可以占用多少内存。

相关问题