19

更新:このメソッドがスレッド セーフでない場合は許容されますが、スレッド セーフにする方法を知りたいと思っています。keyまた、回避できる場合は、すべての値に対して単一のオブジェクトをロックしたくありません。

元の質問:キーと関数を受け取り、指定されたキーでオブジェクトがキャッシュされているかどうかをチェックする高階関数を書きたいとします。が含まれている場合、キャッシュされた値が返されます。それ以外の場合は、指定された関数が実行され、結果がキャッシュされて返されます。

これが私のコードの簡略版です:

public static T CheckCache<T>(string key, Func<T> fn, DateTime expires)
{
    object cache = HttpContext.Current.Cache.Get(key);
    //clearly not thread safe, two threads could both evaluate the below condition as true
    //what can I lock on since the value of "key" may not be known at compile time?
    if (cache == null)
    {
        T result = fn();
        HttpContext.Current.Cache.Insert(key, result, null, expires, Cache.NoSlidingExpiration);
        return result;
    }
    else
        return (T)cache;
}

keyまた、コンパイル時に可能なすべての値を知っているわけではないとします。

このスレッドを安全にするにはどうすればよいですか? 1 つ以上のスレッドが私の条件を true と評価するのを防ぐために、ここでロックを導入する必要があることはわかっていますが、何をロックすればよいかわかりません。ロックについて読んだ例の多く ( Jon Skeet の記事など) では、ロック専用の「ダミー」プライベート変数を使用することを推奨しています。この場合、コンパイル時にキーが不明であるため、これは不可能です。すべての に同じロックを使用することで、このスレッドを簡単に安全にできることはわかっていますkeyが、それは無駄になる可能性があります。

さて、私の主な質問は次のとおりです。

ロックオンは可能keyですか?ここで文字列インターンが役立ちますか?

.NET 2.0 string interning inside outを読んだ後、明示的に呼び出しString.Intern()て、文字列の値から文字列のインスタンスへの 1 対 1 のマッピングを取得 できることを理解しました。これはロックオンに適していますか?上記のコードを次のように変更しましょう。

public static T CheckCache<T>(string key, Func<T> fn, DateTime expires)
{
    //check for the scenario where two strings with the same value are stored at different memory locations
    key = String.Intern(key); 
    lock (key) //is this object suitable for locking?
    {
        object cache = HttpContext.Current.Cache.Get(key);
        if (cache == null)
        {
            T result = fn();
            HttpContext.Current.Cache.Insert(key, result, null, expires, Cache.NoSlidingExpiration);
            return result;
        }
        else
            return (T)cache;
    }
}

上記の実装はスレッドセーフですか?

4

6 に答える 6

6

ダニエルの答えの変形...

文字列ごとに新しいロック オブジェクトを作成するのではなく、文字列のハッシュコードに応じて使用するロックを選択して、小さなロック セットを共有できます。これは、数千または数百万のキーがある可能性がある場合に GC のプレッシャーが少なくなることを意味し、重大なブロックを回避するのに十分な粒度を許可する必要があります (おそらく、必要に応じていくつかの調整を行った後)。

public static T CheckCache<T>(string key, Func<T> fn, DateTime expires)
{
    object cached = HttpContext.Current.Cache[key];
    if (cached != null)
        return (T)cached;

    int stripeIndex = (key.GetHashCode() & 0x7FFFFFFF) % _stripes.Length;

    lock (_stripes[stripeIndex])
    {
        T result = fn();
        HttpContext.Current.Cache.Insert(key, result, null, expires,
                                         Cache.NoSlidingExpiration);
        return result;
    }
}

// share a set of 32 locks
private static readonly object[] _stripes = Enumerable.Range(0, 32)
                                                      .Select(x => new object())
                                                      .ToArray();

_stripesこれにより、配列内の要素の数を変更するだけで、特定のニーズに合わせてロックの粒度を微調整できます。(ただし、文字列ごとに1つのロックに近い粒度が必要な場合は、ダニエルの答えを使用することをお勧めします。)

于 2011-08-08T15:22:01.420 に答える
3

決して弦をロックしないでください。特に、インターンされている人について。インターンされた文字列をロックする危険性については、このブログ エントリを参照してください。

新しいオブジェクトを作成してロックするだけです。

object myLock = new object();
于 2011-08-08T14:35:13.847 に答える
3

ドキュメントによると、キャッシュ タイプはスレッド セーフです。したがって、自分自身を同期しないことの欠点は、アイテムが作成されているときに、他のスレッドがアイテムを作成する必要がないことに気付く前に、アイテムが数回作成される可能性があることです。

状況が単純に一般的な静的/読み取り専用のものをキャッシュすることである場合は、発生する可能性のあるいくつかの奇妙な衝突を保存するためだけに同期する必要はありません。(衝突が無害であると仮定します。)

ロック オブジェクトは文字列に固有ではなく、必要なロックの粒度に固有です。この場合、キャッシュへのアクセスをロックしようとしているので、1 つのオブジェクトがキャッシュのロックを処理します。入ってくる特定のキーをロックするという考えは、ロックが通常関係している概念ではありません。

高価な呼び出しが複数回発生するのを止めたい場合は、ロードロジックを新しいクラスにリッピングし、Oded の回答に従って、内部ロックオブジェクトをLoadMillionsOfRecords呼び出して一度ロックすることができます。.Load

于 2011-08-08T14:37:01.197 に答える
3

私は実用的なアプローチを採用し、ダミー変数を使用します。
何らかの理由でこれが不可能な場合は、Dictionary<TKey, TValue>withkeyをキーとして使用し、ダミー オブジェクトを値として使用して、その値をロックします。文字列はロックに適していないためです。

private object _syncRoot = new Object();
private Dictionary<string, object> _syncRoots = new Dictionary<string, object>();

public static T CheckCache<T>(string key, Func<T> fn, DateTime expires)
{
    object keySyncRoot;
    lock(_syncRoot)
    {

        if(!_syncRoots.TryGetValue(key, out keySyncRoot))
        {
            keySyncRoot = new object();
            _syncRoots[key] = keySyncRoot;
        }
    }
    lock(keySyncRoot)
    {

        object cache = HttpContext.Current.Cache.Get(key);
        if (cache == null)
        {
            T result = fn();
            HttpContext.Current.Cache.Insert(key, result, null, expires, 
                                             Cache.NoSlidingExpiration);
            return result;
        }
        else
            return (T)cache;
    }
}

ただし、ほとんどの場合、これは過剰で不必要なマイクロ最適化です。

于 2011-08-08T14:37:32.303 に答える
0

@eugene-beresovsky answerに触発されたBardock.Utilsパッケージにソリューションを追加しました。

使用法:

private static LockeableObjectFactory<string> _lockeableStringFactory = 
    new LockeableObjectFactory<string>();

string key = ...;

lock (_lockeableStringFactory.Get(key))
{
    ...
}

ソリューション コード:

namespace Bardock.Utils.Sync
{
    /// <summary>
    /// Creates objects based on instances of TSeed that can be used to acquire an exclusive lock.
    /// Instanciate one factory for every use case you might have.
    /// Inspired by Eugene Beresovsky's solution: https://stackoverflow.com/a/19375402
    /// </summary>
    /// <typeparam name="TSeed">Type of the object you want lock on</typeparam>
    public class LockeableObjectFactory<TSeed>
    {
        private readonly ConcurrentDictionary<TSeed, object> _lockeableObjects = new ConcurrentDictionary<TSeed, object>();

        /// <summary>
        /// Creates or uses an existing object instance by specified seed
        /// </summary>
        /// <param name="seed">
        /// The object used to generate a new lockeable object.
        /// The default EqualityComparer<TSeed> is used to determine if two seeds are equal. 
        /// The same object instance is returned for equal seeds, otherwise a new object is created.
        /// </param>
        public object Get(TSeed seed)
        {
            return _lockeableObjects.GetOrAdd(seed, valueFactory: x => new object());
        }
    }
}
于 2015-06-30T21:03:28.117 に答える