11

私がこの関数を持っているとしましょう(スレッドセーフな方法でキャッシュにアクセスしていると仮定します):

object GetCachedValue(string id)
{
    if (!Cache.ContainsKey(id))
    {
         //long running operation to fetch the value for id
         object value = GetTheValueForId(id);
         Cache.Add(id, value);
    }     
    return Cache[id];
}

2つのスレッドが同じ値で同時に「長時間実行操作」を実行しないようにしたい。もちろん、すべてをlock()でラップすることはできますが、値に関係なく関数全体がブロックされるため、2つのスレッドが異なるIDを探している限り、長時間実行される操作を実行できるようにする必要があります。

値に基づいてロックする組み込みのロックメカニズムがあり、一方のスレッドがブロックでき、もう一方のスレッドが長時間実行操作を完了して、2回(またはN回)実行する必要がありませんか?理想的には、長時間実行される操作が1つのスレッドで実行されている限り、他のスレッドが同じID値に対してそれを実行できないようにする必要があります。

IDをHashSetに入れ、操作が完了したら削除することで自分自身をロールバックできますが、それはハックのようです。

4

5 に答える 5

7

ここで使用Lazy<T>します。以下のコードはキャッシュをロックし、をキャッシュに入れてLazyすぐに戻ります。長時間実行操作は、スレッドセーフな方法で1回実行されます。

new Thread(() => Console.WriteLine("1-" + GetCachedValue("1").Value)).Start();
new Thread(() => Console.WriteLine("2-" + GetCachedValue("1").Value)).Start();

Lazy<object> GetCachedValue(string id)
{
    lock (Cache)
    {
        if (!Cache.ContainsKey(id))
        {
            Lazy<object> lazy = new Lazy<object>(() =>
                {
                    Console.WriteLine("**Long Running Job**");
                    Thread.Sleep(3000);
                    return int.Parse(id);
                }, 
                true);

            Cache.Add(id, lazy);
            Console.WriteLine("added to cache");
        }
        return Cache[id];
    }
}
于 2012-12-28T17:31:28.263 に答える
1

この場合、私はこのようなインターフェースが欲しいです

using (SyncDispatcher.Enter(id))
{
    //any code here...
}

したがって、任意のコードを実行でき、idが同じであればスレッドセーフになります。キャッシュから値を取得する必要がある場合は、同時実行呼び出しがないため、簡単に取得できます。

SyncDispatcherの私の実装はこれです:

public class SyncDispatcher : IDisposable
{
    private static object _lock = new object();
    private static Dictionary<object, SyncDispatcher> _container = new Dictionary<object, SyncDispatcher>();

    private AutoResetEvent _syncEvent = new AutoResetEvent(true);

    private SyncDispatcher() { }

    private void Lock()
    {
        _syncEvent.WaitOne();
    }

    public void Dispose()
    {
        _syncEvent.Set();
    }

    public static SyncDispatcher Enter(object obj)
    {
        var objDispatcher = GetSyncDispatcher(obj);
        objDispatcher.Lock();

        return objDispatcher;
    }

    private static SyncDispatcher GetSyncDispatcher(object obj)
    {
        lock (_lock)
        {
            if (!_container.ContainsKey(obj))
            {
                _container.Add(obj, new SyncDispatcher());
            }

            return _container[obj];
        }
    }
}

簡単なテスト:

static void Main(string[] args)
{
    new Thread(() => Execute("1", 1000, "Resource 1")).Start();
    new Thread(() => Execute("2", 200, "Resource 2")).Start();
    new Thread(() => Execute("1", 0, "Resource 1 again")).Start();  
}

static void Execute(object id, int timeout, string message)
{
    using (SyncDispatcher.Enter(id))
    {
        Thread.Sleep(timeout);

        Console.WriteLine(message);              
    }
}

ここに画像の説明を入力してください

于 2012-12-29T12:05:09.530 に答える
0

コメントがある場所にロックを移動します。現在実行中の長時間実行操作のリストを維持し、そのリストへのアクセスをロックし、探しているものがそのリストにないGetValueForId場合にのみ実行する必要があると思います。id何かを作ってみます。

private List<string> m_runningCacheIds = new List<string>();

object GetCachedValue(string id)
{
    if (!Cache.ContainsKey(id))
    {
         lock (m_runningCacheIds) {
             if (m_runningCacheIds.Contains(id)) {
                 // Do something to wait until the other Get is done....
             }
             else {
                 m_runningCacheIds.Add(id);
             }
         }

         //long running operation to fetch the value for id

         object value = GetTheValueForId(id);
         Cache.Add(id, value);

         lock (m_runningCacheIds)
             m_runningCacheIds.Remove(id);
    }     
    return Cache[id];
}

他のスレッドが値を取得するのを待っている間、スレッドが何をするのかという問題がまだあります。

于 2012-12-28T17:17:39.023 に答える
0

その場合、Mutexを次のように使用します。

object GetCachedValue(string Key)
{
    // note here that I use the key as the name of the mutex
    // also here you need to check that the key have no invalid charater
    //   to used as mutex name.
    var mut = new Mutex(true, key);

    try
    {   
        // Wait until it is safe to enter.
        mut.WaitOne();

        // here you create your cache
        if (!Cache.ContainsKey(Key))
        {
             //long running operation to fetch the value for id
             object value = GetTheValueForId(Key);
             Cache.Add(Key, value);
        }     

        return Cache[Key];        
    }
    finally
    {
        // Release the Mutex.
        mut.ReleaseMutex();
    }   
}

ノート:

  • 一部の文字はミューテックス名には無効です(スラッシュなど)
  • 使用するアプリケーション(またはWebプール)ごとにキャッシュが異なり、asp.netのキャッシュについて話す場合、ミューテックスはコンピューター内のすべてのスレッドとプールをロックします。この場合、私も使用します。キーに追加する静的ランダム整数であり、キーごとだけでなくプールごとにもロックを変更しません。
于 2012-12-28T23:41:59.360 に答える
-2

これは世界で最も洗練されたソリューションではありませんが、ダブルチェックとロックでこの問題を回避しました。

object GetCachedValue(string id)
{
    if (!Cache.ContainsKey(id))
    {
         lock (_staticObj)
         {
            if (!Cache.ContainsKey(id))
            {
               //long running operation to fetch the value for id
               object value = GetTheValueForId(id);
               Cache.Add(id, value);
            }
         }
    }     
    return Cache[id];
}
于 2012-12-28T17:17:53.857 に答える