这是延迟加载缓存实现线程安全的? [英] Is this lazy-loading cache implementation thread-safe?

查看:130
本文介绍了这是延迟加载缓存实现线程安全的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发的3.5 .NET Framework和我需要使用一个缓存的多线程塞纳里奥与它的项目延迟加载模式。 阅读在网络上的几篇文章后,我试着写我自己的实现。 这是我的code。

I'm developing with the 3.5 .NET Framework and I need to use a cache in a multithread senario with lazy-loading pattern for its items. After reading several articles on the web I've tried to write my own implementation. Here's my code.

public class CacheItem
{
    public void ExpensiveLoad()
    {
        // some expensive code
    }
}
public class Cache
{
    static object SynchObj = new object();
    static Dictionary<string, CacheItem> Cache = new Dictionary<string, CacheItem>();
    static volatile List<string> CacheKeys = new List<string>();

    public CacheItem Get(string key)
    {
        List<string> keys = CacheKeys;
        if (!keys.Contains(key))
        {
            lock (SynchObj)
            {
                keys = CacheKeys;
                if (!keys.Contains(key))
                {
                    CacheItem item = new CacheItem();
                    item.ExpensiveLoad();
                    Cache.Add(key, item);
                    List<string> newKeys = new List<string>(CacheKeys);
                    newKeys.Add(key);
                    CacheKeys = newKeys;
                }
            }
        }
        return Cache[key];
    }
}

正如你可以看到缓存对象使用两个字典至极存储真正的键 - 值对和一个列表至极仅复制的钥匙。 当一个线程调用get方法读取静态共享密钥列表(至极被声明为volatile),并调用包含方法,看看最关键的是已经present如果不使用双重检查锁定模式开始懒惰之前,负载。在装载的端部的键列表的新实例被创建并存储在静态变量

As you can see the Cache object uses both a dictionary wich stores the real key-value pairs and a list wich duplicates only the keys. When a thread calls the Get method it reads the static shared key list (wich is declared volatile) and call the Contains method to see if the key is already present and if not uses a double-checked lock pattern before starting the lazy loading. At the end of the loading a new instance of the key list is created and stored in the static variable.

显然,我在的情况下重新创建密钥的整个列表的费用是针对单个项目载荷的成本几乎毫无关系。

Obviously I'm in a situation where the cost of recreating the entire list of keys is almost irrelevant against the cost of a single item loading.

我希望有人能告诉我,如果真的是线程安全的。 当我说线程安全的我的意思是每一位读者的线程可避免损坏或脏读和每一个作家线程加载缺少的项目只有一次。

I hope someone can tell me if it is really thread-safe. When I say "thread-safe" I mean that every reader thread can avoid corrupted or dirty reads and every writer thread loads the missing item only once.

感谢。

推荐答案

这是不是线程安全的,因为你读的字典时不锁。

This is not thread safe since you aren't locking when reading the Dictionary.

有一个竞赛​​状态,其中一个线程可以读:

There is a race condition whereby one thread can be reading:

return Cache[key];

而另一种是写:

while another is writing:

_Cache.Add(key, item);

由于 MSDN文档词典&LT; TKEY的,TValue&GT; 状态:`

As the MSDN documentation for Dictionary<TKey,TValue> states: `

要允许多个线程读取和写入访问的集合,您必须实现自己的同步。

To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

和您的synchorization不包括阅读器。

and your synchorization does not include the reader.

您真的需要使用一个线程安全的字典,这将极大地简化您的code(你不需要列表的话)

You really need to use a thread-safe dictionary, which will simplify your code enormously (you won't need the List at all)

我会建议得到了源.NET 4 ConcurrentDictionary。

I'd recommend getting the source for the .NET 4 ConcurrentDictionary.

获取线程安全的权利是很难的,因为是事实,一些其他的回答者都错误地指出你的实现是线程安全的证明。因此,我相信微软实施前一个自制的。

Getting thread safety right is hard, as is evidenced by the fact that some of the other answerers are incorrectly stating that your implementation is thread-safe. Hence I'd trust Microsoft's implementation before a home made one.

如果你不希望使用一个线程安全的字典,那么我建议你简单的东西,如:

If you don't want to use a thread-safe dictionary, then I'd recommend something simple like:

public CacheItem Get(string key)
{
    lock (SynchObj)
    {
        CacheItem item;
        if (!Cache.TryGetValue(key, out item))
        {
            item = new CacheItem();
            item.ExpensiveLoad();
            Cache.Add(key, item);
        }
        return item;
    }
}

您也可以尝试与实施 ReaderWriterLockSlim ,虽然你可能无法得到一个显著的性能提升(谷歌ReaderWriterLockSlim性能)。

You could also try an implementation with a ReaderWriterLockSlim, though you might not get a significant performance improvement (google for ReaderWriterLockSlim performance).

作为使用ConcurrentDictionary的实现,在大多数情况下,我会简单地使用这样的:

As for an implementation using a ConcurrentDictionary, in most cases I would simply use something like:

static ConcurrentDictionary<string, CacheItem> Cache = 
    new ConcurrentDictionary<string, CacheItem>(StringComparer.Ordinal);
...
CacheItem item = Cache.GetOrAdd(key, key => ExpensiveLoad(key));

这会导致 ExpensiveLoad 被称为多,一旦每个键,但我敢打赌,如果您分析您的应用程序,你会发现,这是很罕见的不是一个问题。

This can result in ExpensiveLoad being called more that once for each key, but I bet if you profile your app you'll find that this is so rare as to not be a problem.

如果你真的坚持确保它只被调用一次,那么你可以弄个.NET 4的延迟&LT; T&GT; 的实施和执行类似:

If you really insist on ensuring it's only called once, then you could get hold of the .NET 4 Lazy<T> implementation and do something like:

static ConcurrentDictionary<string, Lazy<CacheItem>> Cache = 
    new ConcurrentDictionary<string, Lazy<CacheItem>>(StringComparer.Ordinal);
...

CacheItem item = Cache.GetOrAdd(key, 
               new Lazy<CacheItem>(()=> ExpensiveLoad(key))
             ).Value;

在这个版本中,多个延迟&LT; CacheItem&GT; 情况下,可能会产生,但只有一个实际上将被存储在字典中。 ExpensiveLoad 将被称为第一次延迟&LT; CacheItem&GT; .value的被废弃存储在字典中的实例。 这延迟&LT; T&GT; 构造函数使用LazyThreadSafetyMode.ExecutionAndPublication它采用了锁内部,以便确保只有一个线程调用工厂方法 ExpensiveLoad

In this version, multiple Lazy<CacheItem> instances might be created, but only one will actually be stored in the dictionary. ExpensiveLoad will be called the first time Lazy<CacheItem>.Value is dereferenced for the instance stored in the dictionary. This Lazy<T> constructor uses LazyThreadSafetyMode.ExecutionAndPublication which uses a lock internally so ensure only one thread calls the factory method ExpensiveLoad.

顺便说一句,有一个字符串键构建任何字典的时候,我总是用的IEqualityComparer&LT;字符串&GT; 参数(通常StringComparer.Ordinal或StringComparer.OrdinalIgnoreCase)明确有关文件区分大小写的意向。

As an aside, when constructing any dictionary with a string key, I always use the IEqualityComparer<string> parameter (usually StringComparer.Ordinal or StringComparer.OrdinalIgnoreCase) to explicitly document the intention regarding case-sensitivity.

这篇关于这是延迟加载缓存实现线程安全的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆