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

0

参考までに、Servy の例では、へのLazy呼び出しごとに createdのインスタンスを取得しますGetOrAdd。今でも魔法のLazyようなことが起こり、Funcインスタンスを作成する呼び出しは 1 回だけです。しかし、上記の例の の余分なインスタンス化がLazy、試したときに見たメモリの増加を説明しているのかもしれません。

"double" ラムダを作成すると、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 に答える