3

我试图通过为一些非常中心的函数引入缓存层来从我的数据库服务器卸载工作,这些函数将值插入数据库中的表并检索 id。这是在多线程环境中。

我的第一种方法是:

public class Cache {
      private Dictionary<string, Int64> i;

      public void Init() { /* init i with values from DB */ }

      public Int64 Get(string value)
         lock(i) {
            Int64 id;
            if (cache.i.TryGetValue(value, out id))
                return id;

            id = /* Insert to DB and retrieve ID */
            cache.i[value] = id;
            return id;
      }
 }

这有帮助。然而,线程仍然彼此等待很多。我想减少这个等待时间。我的第一个想法是使用ConcurrentDictionary.GetOrAdd(key, valueFactory). 这是行不通的,因为 valueFactory 可以被多次调用。

我已经结束了这种方法:

public class Cache
{
    private ConcurrentDictionary<string, Int64> i;

    public void Init() { /* init i with values from DB */ }

    public Int64 Get(string value)
    {
        Int64 id;
        if (i.TryGetValue(value, out id))
            return id;

        lock (i)
        {
            if (i.TryGetValue(value, out id))
                return id;

            id = /* Insert to DB and retrieve ID */
            i.TryAdd(value, id);
            return id;
        }
    }

有没有更好的方法来做到这一点?这甚至是线程安全的吗?

4

2 回答 2

11

您要做的是懒惰地创建一个对象,该对象只需要创建一次,然后在创建后由任意数量的线程访问。 Lazy正是为此而设计的:

public class Cache
{
    private ConcurrentDictionary<string, Lazy<long>> i;

    public void Init() { /* init i with values from DB */ }

    public Int64 Get(string value)
    {
        return i.GetOrAdd(value, new Lazy<long>(() =>
            CreateDatabaseRecordAndGetId()))
            .Value;
    }

    private long CreateDatabaseRecordAndGetId()
    {
        throw new NotImplementedException();
    }
}
于 2014-07-23T19:56:38.987 回答
0

仅供参考,在 Servy 的示例中Lazy,每次调用GetOrAdd. 现在,仍然会发生神奇的Lazy事情,并且您只会接到一个Func创建实例的调用。但也许Lazy上面示例中的额外实例解释了您在尝试时看到的内存增加。

如果您创建一个“双” lambda,您不会获得多个 Lazy 实例化。

例如,将其粘贴到控制台应用程序中,并比较使用和不使用x => new Lazy...以下内容的实现:

public static class LazyEvaluationTesting
{
    private static readonly ConcurrentDictionary<int, CustomLazy<CacheableItem>>
        cacheableItemCache = new ConcurrentDictionary<int, CustomLazy<CacheableItem>>();

    private static CacheableItem RetrieveCacheableItem(int itemId)
    {
        Console.WriteLine("--RETRIEVE called\t ItemId [{0}] ThreadId [{1}]", itemId, Thread.CurrentThread.ManagedThreadId);
        return new CacheableItem
        {
            ItemId = itemId
        };
    }

    private static void GetCacheableItem(int itemId)
    {
        Console.WriteLine("GET called\t ItemId [{0}] ThreadId [{1}]", itemId, Thread.CurrentThread.ManagedThreadId);

        CacheableItem cacheableItem = cacheableItemCache
            .GetOrAdd(itemId,
                x => new CustomLazy<CacheableItem>(
                    () => RetrieveCacheableItem(itemId)
                )
            ).Value;

        //CacheableItem cacheableItem2 = cacheableItemCache
        //  .GetOrAdd(itemId,
        //      new CustomLazy<CacheableItem>(
        //          () => RetrieveCacheableItem(itemId)
        //      )
        //  ).Value;
    }

    public static void TestLazyEvaluation()
    {
        int[] itemIds = { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 };
        ParallelOptions options = new ParallelOptions
        {
            MaxDegreeOfParallelism = 75
        };

        Parallel.ForEach(itemIds, options, itemId =>
        {
            GetCacheableItem(itemId);
            GetCacheableItem(itemId);
            GetCacheableItem(itemId);
            GetCacheableItem(itemId);
            GetCacheableItem(itemId);
        });
    }

    private class CustomLazy<T> : Lazy<T> where T : class
    {
        public CustomLazy(Func<T> valueFactory)
            : base(valueFactory)
        {
            Console.WriteLine("-Lazy Constructor called  ThreadId [{0}]", Thread.CurrentThread.ManagedThreadId);
        }
    }

    private class CacheableItem
    {
        public int ItemId { get; set; }
    }
}

资料来源:Reed Copsey 的博客

于 2015-02-23T22:53:40.103 回答