Использование строки в качестве блокировки для синхронизации потоков
Пока я смотрел какой-то старый код приложения, я заметил, что он использует строковый объект для синхронизации потоков. Я пытаюсь решить некоторые проблемы с конфликтом в этой программе и задавался вопросом, может ли это привести к некоторым странным ситуациям. Есть предположения?
private static string mutex= "ABC";
internal static void Foo(Rpc rpc)
{
lock (mutex)
{
//do something
}
}
Ответы
Ответ 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();
}
}