2013-06-18 3 views
3

У меня есть этот код C#:Почему этот код не заканчивается в тупике

public class Locking 
{ 

    private int Value1; private int Value2; 

    private object lockValue = new Object(); 
    public int GetInt1(int value1, int value2) 
    { 
     lock (lockValue) 
     { 
      Value1 = value1; 
      Value2 = value2; 
      return GetResult(); 
     } 

    } 

    public int GetInt2(int value1, int value2) 
    { 
     lock (lockValue) 
     { 
      return GetInt1(value1, value2); 
     } 
    } 

    private int GetResult() 
    { 
     return Value1 + Value2; 
    } 


} 

Поэтому в основном я ожидаю в тупик, если я исполню GetInt2 но код просто выполняет. Любое хорошее объяснение.

ответ

4

Общий случай: является ли объект синхронизации повторным абитуриентом. Другими словами, можно снова получить тот же поток, если он уже владеет блокировкой. Другой способ сказать, является ли объект «сродством к потоку».

В .NET класс Monitor (который реализует оператор блокировки), Mutex и ReaderWriterLock являются повторными. Классы Семафор и СемафорСлим - , а не, вы можете получить код в тупик с помощью двоичного семафора. Самый дешевый способ реализации блокировки - с Interlocked.CompareExchange(), он также не будет повторно включен.

Существует дополнительная стоимость, связанная с созданием повторного входа объекта синхронизации, ему необходимо отслеживать, какой поток принадлежит ему, и как часто блокировка была получена на принадлежащем потоке. Для этого требуется хранить Thread.ManagedId и счетчик, два ints. Это повлияло на выбор в C++, например, спецификацию языка C++ 11, наконец, добавив потоки в стандартную библиотеку. Класс std :: mutex: не повторный участник на этом языке, и предложения по добавлению рекурсивной версии были отклонены. Они считали накладные расходы на то, чтобы сделать его повторным участником слишком высоким. Немного тяжело, возможно, стоимость, которая довольно незначительна против количества времени, затрачиваемого на отладку случайного тупика :) Но это язык, на котором нет ни малейшего шанса, что получение идентификатора потока может быть гарантировано дешевым, как оно есть. СЕТЬ.

Это показано в классе ReaderWriterLockSlim, который вы можете выбрать. Обратите внимание на свойство RecursionPolicy, позволяющее выбирать между NoRecursion и SupportsRecursion.Режим NoRecursion дешевле и делает его действительно slim.

+0

Семафоры работают так, как я и предполагал, вот мой код http://stackoverflow.com/a/17194374/98491 Это просто 'private static ss = new SemaphoreSlim (1, 1);' и в коде я завернул 'ss. Wait(); 'и' ss.Release() 'в блок try/finally. –

+0

Отличный ответ! +1 для объяснения того, как работают все замки, а не только того, кто спрашивает о – Thresh

11

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

В этом случае выполняется только один поток; он принимает блокировку на lockValue в GetInt2, затем переходит в GetInt1, где снова встречает оператор блокировки на lockValue, который он уже имеет, поэтому разрешено продолжить.

+3

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

7

Оператор lock в C# представляет собой синтаксический сахар, интерпретируемый компилятором как вызов Monitor.Enter. Это documented (в разделе "Мониторы"), что

lock (x) 
{ 
    DoSomething(); 
} 

эквивалентно

System.Object obj = (System.Object)x; 
System.Threading.Monitor.Enter(obj); 
try 
{ 
    DoSomething(); 
} 
finally 
{ 
    System.Threading.Monitor.Exit(obj); 
} 

Документация Monitor.Enter утверждает, что

Это законно и тот же поток, чтобы вызвать Enter не один раз без блокировки; однако равное количество звонков Exit должно быть , вызванное перед тем, как другие потоки, ожидающие на объекте, будут разблокированы.

Из вышеизложенного очевидно, что данный код не будет создавать тупик, если задействован только один поток.

Смежные вопросы