使用ConcurrentDictionary实现缓存

问题描述 投票:0回答:2

我正在尝试为 API 创建自己的缓存实现。这是我第一次使用

ConcurrentDictionary
,我不知道我是否正确使用它。在测试中,有些东西抛出了错误,到目前为止我还无法再次重现它。出了什么问题?

private static readonly ConcurrentDictionary<string, ThrottleInfo> CacheList =
    new ConcurrentDictionary<string, ThrottleInfo>();

public override void OnActionExecuting(HttpActionContext actionExecutingContext)
{
    if (CacheList.TryGetValue(userIdentifier, out var throttleInfo))
    {
        if (DateTime.Now >= throttleInfo.ExpiresOn)
        {
            if (CacheList.TryRemove(userIdentifier, out _))
            {
                //TODO:
            }
        }
        else
        {
            if (throttleInfo.RequestCount >= defaultMaxRequest)
            {
                actionExecutingContext.Response = ResponseMessageExtension
                    .TooManyRequestHttpResponseMessage();
            }
            else
            {
                throttleInfo.Increment();
            }
        }
    }
    else
    {
        if (CacheList.TryAdd(userIdentifier, new ThrottleInfo(Seconds)))
        {
            //TODO:
        }
    }
}

public class ThrottleInfo
{
    private int _requestCount;

    public int RequestCount => _requestCount;

    public ThrottleInfo(int addSeconds)
    {
        Interlocked.Increment(ref _requestCount);
        ExpiresOn = ExpiresOn.AddSeconds(addSeconds);
    }

    public void Increment()
    {
        // this is about as thread safe as you can get.
        // From MSDN: Increments a specified variable and stores the result,
        // as an atomic operation.
        Interlocked.Increment(ref _requestCount);

        // you can return the result of Increment if you want the new value,
        // but DO NOT set the counter to the result.
        // [i.e. counter = Interlocked.Increment(ref counter);]
        // This will break the atomicity.
    }

    public DateTime ExpiresOn { get; } = DateTime.Now;
}
c# multithreading thread-safety concurrentdictionary
2个回答
0
投票

如果我明白您在 ExpiresOn 已通过时要做什么,请删除该条目,否则更新它或添加(如果不存在)。 您当然可以利用 AddOrUpdateMethod 来简化一些代码。 看看这里的一些很好的例子:https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/how-to-add-and-remove-items 希望这有帮助。


0
投票

ConcurrentDictionary
仅在以下情况下才足以作为线程安全容器:(1)需要保护的整个状态是其内部状态(它包含的键和值),并且仅当(2)此状态可以使用它提供的专用 API 进行原子突变(
GetOrAdd
AddOrUpdate
)。在您的情况下,不满足第二个要求,因为您需要根据其值的状态有条件地删除键,并且
ConcurrentDictionary
类不支持这种情况。

所以你当前的缓存实现不是线程安全的。偶尔抛出异常的事实纯属巧合。如果它是完全防抛出的,它仍然是非线程安全的,因为它不会完全防错,这意味着它可能偶尔(或永久)转换到与其规范不兼容的状态(例如返回过期值) ).

关于

ThrottleInfo
类,它存在一个可见性错误,如果您在一台机器上广泛测试该类,则该错误可能不会被观察到,然后当您在另一台具有不同 CPU 架构的机器上部署应用程序时突然出现。非 volatile
private int _requestCount
字段通过公共属性
RequestCount
公开,因此不能保证(基于 C# 规范)所有线程都会看到其最新值。您可以阅读 Igor Ostrovsky 的这篇文章,了解内存模型的特殊性,这可能会让您(像我一样)相信,在多线程代码中使用无锁技术(在本例中使用
Interlocked
类)比在多线程代码中使用更麻烦。这是值得的。如果您阅读并喜欢它,还有本文的第 2 部分。

© www.soinside.com 2019 - 2024. All rights reserved.