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

Использование строки в качестве блокировки для синхронизации потоков

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

private static string mutex= "ABC";

internal static void Foo(Rpc rpc)
{
    lock (mutex)
    {
        //do something
    }
}
4b9b3361

Ответ 1

Строки, подобные этому (из кода), могут быть " interned". Это означает, что все экземпляры "ABC" указывают на один и тот же объект. Даже через AppDomain вы можете указать один и тот же объект (спасибо Steven для подсказки).

Если у вас много строковых мьютексов, из разных мест, но с одним и тем же текстом, они могут блокировать один и тот же объект.

Внутренний пул сохраняет хранилище строк. Если вы назначаете литеральную строковую константу нескольким переменным, каждая переменная устанавливается так, чтобы ссылаться на одну и ту же константу в станем пуле вместо ссылки на несколько разных экземпляров String, которые имеют одинаковые значения.

Лучше использовать:

 private static readonly object mutex = new object();

Кроме того, поскольку ваша строка не является const или readonly, вы можете ее изменить. Поэтому (теоретически) можно заблокировать ваш мьютекс. Измените мьютекс на другую ссылку, а затем введите критический раздел, потому что блокировка использует другой объект/ссылку. Пример:

private static string mutex = "1";
private static string mutex2 = "1";  // for 'lock' mutex2 and mutex are the same

private static void CriticalButFlawedMethod() {
    lock(mutex) {
      mutex += "."; // Hey, now mutex points to another reference/object
      // You are free to re-enter
      ...
    }
}

Ответ 2

Чтобы ответить на ваш вопрос (как уже отмечали некоторые другие), есть некоторые потенциальные проблемы с приведенным вами примером кода:

private static string mutex= "ABC";
  • Переменная mutex не является неизменной.
  • Строковый литерал "ABC" будет ссылаться на ту же внутреннюю ссылку на объект везде в вашем приложении.

В общем, я бы посоветовал не блокировать строки. Однако есть случай, когда я столкнулся с тем, где это полезно.

Были случаи, когда я поддерживал словарь блокирующих объектов, где ключ является чем-то уникальным в отношении некоторых данных, которые у меня есть. Здесь надуманный пример:

void Main()
{
    var a = new SomeEntity{ Id = 1 };
    var b = new SomeEntity{ Id = 2 };

    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(b));    
    Task.Run(() => DoSomething(b));
}

ConcurrentDictionary<int, object> _locks = new ConcurrentDictionary<int, object>();    
void DoSomething(SomeEntity entity)
{   
    var mutex = _locks.GetOrAdd(entity.Id, id => new object());

    lock(mutex)
    {
        Console.WriteLine("Inside {0}", entity.Id);
        // do some work
    }
}   

Цель такого кода - сериализовать параллельные вызовы DoSomething() в контексте объекта Id. Недостатком является словарь. Чем больше объектов, тем больше. Это также просто больше кода, чтобы читать и думать.

Я думаю, что интернирование строк в .NET может упростить:

void Main()
{
    var a = new SomeEntity{ Id = 1 };
    var b = new SomeEntity{ Id = 2 };

    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(b));    
    Task.Run(() => DoSomething(b));
}

void DoSomething(SomeEntity entity)
{   
    lock(string.Intern("dee9e550-50b5-41ae-af70-f03797ff2a5d:" + entity.Id))
    {
        Console.WriteLine("Inside {0}", entity.Id);
        // do some work
    }
}

Разница здесь в том, что я полагаюсь на интернирование строк, чтобы дать мне одну и ту же ссылку на объект на идентификатор объекта. Это упрощает мой код, потому что мне не нужно поддерживать словарь экземпляров mutex.

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

Блокировка строк может быть хорошей идеей или плохой идеей в зависимости от обстоятельств и внимания, которое разработчик предоставляет деталям.

Ответ 3

Если вам нужно заблокировать строку, вы можете создать объект, который связывает строку с объектом, с которым вы можете заблокировать.

class LockableString
{
     public string _String; 
     public object MyLock;  //Provide a lock to the data in.

     public LockableString()
     {
          MyLock = new object();
     }
}