Согласно моему пониманию блокировки не выпущен, пока время выполнения не завершает блок кода блокировки (obj) (потому что, когда блок завершается, он называет Монитор. Выход (obj).
С этим пониманием я не могу понять причину позади поведения следующего кода:
private static string obj = "";
private static void RecurseSome(int number)
{
Console.WriteLine(number);
lock (obj)
{
RecurseSome(++number);
}
}
//Вызов: RecurseSome(0)
//Вывод: 0 1 2 3...... stack overflow exception
Должно быть некоторое понятие, которое я пропускаю. Помогите.
Замок знает, какой поток его заблокировал. Если тот же поток приходит снова, он просто увеличивает счетчик и не блокирует.
Таким образом, в рекурсии второй вызов тоже заходит - и блокировка внутренне увеличивает счетчик блокировки - потому что это тот же поток (который уже держит блокировку).
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_csref/html/656da1a4-707e-4ef6-9c6e-6d13b646af42.htm
Or MSDN: http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx
утверждает:
Ключевое слово lock гарантирует, что один поток не войдет в критическую секцию кода пока другой поток находится в критической секции. Если другой поток пытается войти в заблокированный код, он будет ждать, блокируя, пока объект не освободится.
Обратите внимание на ссылки на потоки и акцент на "ДРУГОЙ" поток.
это непрерывный цикл, потому что нет способа определить, когда остановить рекурсию, а объект, к которому поток пытается получить доступ, всегда блокируется.
Пожалуйста, НЕ блокируйте строковый объект. Это может привести к неожиданному поведению, например, к блокировкам в вашем приложении. В настоящее время вы блокируете пустую строку, что еще хуже. Вся сборка использует одну и ту же пустую строку. И что еще хуже: в качестве оптимизации CLR повторно использует строки в AppDomains. Блокировка строки означает, что вы, возможно, выполняете междоменную блокировку.
Используйте следующий код в качестве объекта блокировки:
private readonly static object obj = new object();
UPDATE
На самом деле, я думаю, можно с уверенностью сказать, что возможность блокировки на чем угодно - это главный недостаток дизайна .NET framework. Вместо этого им следовало бы создать некий SyncRoot
герметичный класс и разрешить только оператору lock
и Monitor.Enter
принимать экземпляры SyncRoot
. Это избавило бы нас от многих страданий. Я понимаю, откуда взялся этот недостаток; в Java есть такая же конструкция.
Как уже отметили другие, замок держится на нити и поэтому будет работать. Однако я хочу кое-что добавить к этому.
Джо Даффи, специалист по параллелизму из Microsoft, имеет несколько правил проектирования, касающихся параллелизма. Одно из его правил проектирования гласит:
9 . Избегайте рекурсии блокировок в вашем проекте. Используйте нерекурсивную блокировку где это возможно.
Рекурсия обычно указывает на чрезмерное упрощение вашей синхронизации, что часто приводит к менее надежному коду. Некоторые конструкции используют рекурсию блокировки как способ избежать разделения функций на те. которые принимают блокировки и те, которые предполагают что блокировки уже взяты. Это может по общему признанию, может привести к уменьшению размера кода и, следовательно, к сокращению время написания, но приводит к более хрупкой конструкции в конечном итоге.
(source)
Чтобы предотвратить рекурсивную блокировку, перепишите код следующим образом:
private readonly static object obj = new object();
private static void Some(int number)
{
lock (obj)
{
RecurseSome(number);
}
}
private static void RecurseSome(int number)
{
Console.WriteLine(number);
RecurseSome(++number);
}
Более того, я ваш код выбросит StackOverflowException
, потому что он никогда не завершит рекурсивный вызов самого себя. Вы могли бы переписать свой метод следующим образом:
private static void RecurseSome(int number)
{
Console.WriteLine(number);
if (number < 100)
{
RecurseSome(++number);
}
}
Блокировка принадлежит текущему потоку. Рекурсивный вызов также выполняется в текущем потоке. Если другой поток попытается получить блокировку, он заблокируется.
Это не имеет ничего общего с блокировкой. Проверьте ваш код рекурсии. где находится пограничный случай для остановки рекурсии?
Если вы спрашиваете об исключении переполнения стека, то это потому, что там нет ничего, чтобы прервать рекурсию. Стековое пространство обычно составляет всего несколько K, и вы исчерпаете его довольно быстро.
Теперь блокировка в этом случае может быть использована для сериализации вывода вызова, так что если вы вызовете RecurseSome из двух разных потоков, вы увидите весь список из первого потока, а затем весь список из второго потока. Без блокировки вывод от двух потоков чередовался бы.
Вы можете достичь тех же результатов без рекурсивного снятия блокировки, разделив метод:
private static void RecurseSome(int number)
{
lock (obj)
{
RecurseSomeImp(number);
}
}
private static void RecurseSomeImp(int number)
{
Console.WriteLine(number);
if( number < 100 ) // Add a boundary condition
RecurseSomeImp(++number);
}
На самом деле это будет работать лучше, так как снятие и освобождение блокировок происходит быстро, но не бесплатно.