Подтвердить что ты не робот

Можно ли использовать строку в качестве объекта блокировки?

Мне нужно сделать критический раздел в области на основе конечного набора строк. Я хочу, чтобы блокировка была разделена для одного и того же экземпляра строки (несколько похожа на подход 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, s => new object()))
        {
            ...
        }
    }
}
4b9b3361

Ответ 1

Блокировка строк не рекомендуется, основная причина заключается в том, что (из-за интерполяции строк) какой-либо другой код мог блокировать один и тот же экземпляр строки, не зная об этом. Создание потенциала для ситуаций взаимоблокировки.

Теперь это, вероятно, очень странный сценарий в большинстве конкретных ситуаций. Это более общее правило для библиотек.

Но, с другой стороны, что такое воспринимаемое преимущество струн?

Итак, точка для точки:

Есть ли проблемы с этим подходом?

Да, но в основном теоретический.

Можно ли блокировать строковый объект таким образом и есть ли проблемы с безопасностью потоков при использовании HashSet?

HashSet<> не участвует в безопасности потоков, пока потоки только читаются одновременно.

Лучше ли, например, создать словарь, который создает новый объект блокировки для каждого экземпляра строки?

Да. Просто быть в безопасности. В большой системе основной целью избежать тупика является сохранение локальных и закрытых объектов блокировки. Доступ к ним должен иметь только ограниченный объем кода.

Ответ 2

Я бы сказал, это очень плохая идея, лично. Это не то, для чего нужны строки.

(Лично мне не нравится тот факт, что у каждого объекта есть монитор в первую очередь, но это немного другое беспокойство.)

Если вам нужен объект, который представляет блокировку, которая может быть разделена между разными экземплярами, почему бы не создать для нее определенный тип? Вы можете легко определить имя замка для диагностических целей, но блокировка действительно не является целью строки. Что-то вроде этого:

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;
    }
}

Учитывая то, что строки иногда интернированы, а иногда нет (таким образом, что иногда бывает трудно распознать простым проверкой), вы можете легко получить случайно закрытые блокировки, где вы их не намеревались.

Ответ 3

Блокировка строк может быть проблематичной, поскольку интернированные строки по существу глобальны.

Интернированные строки для каждого процесса, поэтому они даже разделяются между различными AppDomains. То же самое касается объектов типа (так что не блокируйте typeof (x)).

Ответ 4

У меня была аналогичная проблема недавно, когда я искал хороший способ блокировки раздела кода на основе строкового значения. Здесь то, что мы имеем в наличии на данный момент, которое решает проблему интернированных строк и имеет требуемую гранулярность.

Основная идея состоит в том, чтобы сохранить статический ConcurrentDictionary объектов синхронизации с помощью строкового ключа. Когда поток входит в метод, он немедленно устанавливает блокировку и пытается добавить объект синхронизации в параллельный словарь. Если мы можем добавить к параллельному словарю, это означает, что ни один другой поток не имеет блокировки на основе нашего строкового ключа, и мы можем продолжить нашу работу. В противном случае мы будем использовать объект синхронизации из параллельного словаря, чтобы установить вторую блокировку, которая будет ждать завершения обрабатываемого потока. Когда вторая блокировка отпущена, мы можем попытаться снова добавить текущий объект синхронизации потока в словарь.

Одно слово предостережения: потоки не поставлены в очередь - поэтому, если несколько потоков с одним и тем же строковым ключом одновременно конкурируют за блокировку, нет никаких гарантий относительно порядка, в котором они будут обрабатываться.

Не стесняйтесь критиковать, если вы думаете, что я что-то пропустил.

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);
            }
        }
    }
}