44

文字列の有限集合に基づいて、領域内にクリティカル セクションを作成する必要があります。同じ文字列インスタンスに対してロックを共有する必要があります ( String.Internアプローチに多少似ています)。

以下の実装を検討しています。

public class Foo
{
    private readonly string _s;
    private static readonly HashSet<string> _locks = new HashSet<string>();

    public Foo(string s)
    {
        _s = s;
        _locks.Add(s);
    }

    public void LockMethod()
    {
        lock(_locks.Single(l => l == _s))
        {
            ...
        }
    }
}

このアプローチに問題はありますか? この方法で文字列オブジェクトをロックしても問題ありませHashSet<string>んか?

たとえば、Dictionary<string, object>文字列インスタンスごとに新しいロック オブジェクトを作成する を作成した方がよいでしょうか?


最終的な実装

提案に基づいて、次の実装を行いました。

public class Foo
{
    private readonly string _s;
    private static readonly ConcurrentDictionary<string, object> _locks = new ConcurrentDictionary<string, object>();

    public Foo(string s)
    {
        _s = s;
    }

    public void LockMethod()
    {
        lock(_locks.GetOrAdd(_s, _ => new object()))
        {
            ...
        }
    }
}
4

4 に答える 4

29

文字列のロックは推奨されません。主な理由は、(文字列インターンのため) 他のコードが、知らないうちに同じ文字列インスタンスをロックする可能性があるためです。デッドロック状態の可能性を作成します。

これは、ほとんどの具体的な状況では、おそらく大げさなシナリオです。これは、ライブラリのより一般的なルールです。

しかし一方で、文字列の利点として認識されているものは何ですか?

したがって、ポイントごとに:

このアプローチに問題はありますか?

はい、しかしほとんど理論的です。

この方法で文字列オブジェクトをロックしても問題ありませんか? HashSet を使用する際にスレッド セーフの問題はありますか?

HashSet<>スレッドが同時に読み取りを行うだけである限り、スレッド セーフには関与しません。

たとえば、文字列インスタンスごとに新しいロック オブジェクトを作成するディクショナリを作成する方がよいでしょうか?

はい。安全な側にいるだけです。大規模なシステムでは、デッドロックを回避する主な目的は、ロック オブジェクトをできるだけローカルでプライベートに保つことです。限られた量のコードのみがアクセスできるようにする必要があります。

于 2012-10-09T17:03:58.050 に答える
24

個人的には、それは本当に悪い考えだと思います。それは文字列の目的ではありません。

(個人的には、そもそもすべてのオブジェクトにモニターがあるという事実は嫌いですが、それは少し別の懸念事項です。)

異なるインスタンス間で共有できるロックを表すオブジェクトが必要な場合は、そのための特定のタイプを作成してみませんか? 診断目的で簡単にロックに名前を付けることができますが、ロックは実際には文字列の目的ではありません。このようなもの:

public sealed class Lock
{
    private readonly string name;

    public string Name { get { return name; } }

    public Lock(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }
        this.name = name;
    }
}

文字列がインターンされる場合とインターンされない場合がある方法を考えると(簡単な検査では識別が難しい場合があります)、意図しない場所で誤ってロックを共有してしまう可能性があります。

于 2012-10-09T17:04:09.487 に答える
6

インターンされた文字列は本質的にグローバルであるため、文字列のロックは問題になる可能性があります。

インターンされた文字列はプロセスごとにあるため、異なる AppDomain 間でも共有されます。同じことが型オブジェクトにも当てはまります (そのため、typeof(x) をロックしないでください)。

于 2012-10-09T17:04:04.973 に答える
3

少し前に、文字列値に基づいてコードのセクションをロックする良い方法を探していた同様の問題がありました。現時点では、インターンされた文字列の問題を解決し、必要な粒度を備えたものを次に示します。

主なアイデアは、同期オブジェクトの静的なConcurrentDictionaryを文字列キーで維持することです。スレッドがメソッドに入ると、すぐにロックを確立し、同期オブジェクトを同時実行ディクショナリに追加しようとします。並行辞書に追加できれば、文字列キーに基づいて他のスレッドがロックされていないことを意味し、作業を続行できます。それ以外の場合は、並行辞書の同期オブジェクトを使用して 2 番目のロックを確立し、実行中のスレッドが処理を完了するまで待機します。2 番目のロックが解除されると、現在のスレッドの同期オブジェクトをディクショナリに再度追加することができます。

1 つの注意点: スレッドはキューに入れられないため、同じ文字列キーを持つ複数のスレッドがロックを求めて同時に競合している場合、それらが処理される順序について保証はありません。

私が何かを見落としていると思われる場合は、遠慮なく批判してください。

public class Foo
{
    private static ConcurrentDictionary<string, object> _lockDictionary = new ConcurrentDictionary<string, object>();

    public void DoSomethingThreadCriticalByString(string lockString)
    {
        object thisThreadSyncObject = new object();

        lock (thisThreadSyncObject)
        {
            try
            {
                for (; ; )
                {
                   object runningThreadSyncObject = _lockDictionary.GetOrAdd(lockString, thisThreadSyncObject);
                   if (runningThreadSyncObject == thisThreadSyncObject)
                       break;

                    lock (runningThreadSyncObject)
                    {
                        // Wait for the currently processing thread to finish and try inserting into the dictionary again.
                    }
                }

                // Do your work here.

            }
            finally
            {
                // Remove the key from the lock dictionary
                object dummy;
                _lockDictionary.TryRemove(lockString, out dummy);
            }
        }
    }
}
于 2016-05-29T22:21:14.227 に答える