Использование строки в качестве блокировки для синхронизации потоков - PullRequest
22 голосов
/ 16 ноября 2010

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

private static string mutex= "ABC";

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

Ответы [ 4 ]

36 голосов
/ 16 ноября 2010

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

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

Внутренний пул сохраняет хранилище строк. Если вы присваиваете строковую константу литерала нескольким переменным, каждая переменная устанавливается для ссылки на одну и ту же константу в пуле интернирования вместо ссылки на несколько разных экземпляров 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
      ...
    }
}
24 голосов
/ 26 апреля 2015

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

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

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

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

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

1 голос
/ 30 сентября 2013

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

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

     public LockableString()
     {
          MyLock = new object();
     }
}
0 голосов
/ 16 октября 2018

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

// Returns an Object to Lock with based on a string Value
private static readonly ConditionalWeakTable<string, object> _weakTable = new ConditionalWeakTable<string, object>();
public static object GetLock(string value)
{
    if (value == null) throw new ArgumentNullException(nameof(value));
    return _weakTable.GetOrCreateValue(value.ToLower());
}
...