Используя блокировку (obj) в рекурсивном вызове

Согласно моему пониманию блокировки не выпущен, пока время выполнения не завершает блок кода блокировки (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

Должно быть некоторое понятие, которое я пропускаю. Помогите.

21
задан Amith 31 October 2013 в 09:34
поделиться

7 ответов

Замок знает, какой поток его заблокировал. Если тот же поток приходит снова, он просто увеличивает счетчик и не блокирует.

Таким образом, в рекурсии второй вызов тоже заходит - и блокировка внутренне увеличивает счетчик блокировки - потому что это тот же поток (который уже держит блокировку).

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 гарантирует, что один поток не войдет в критическую секцию кода пока другой поток находится в критической секции. Если другой поток пытается войти в заблокированный код, он будет ждать, блокируя, пока объект не освободится.

Обратите внимание на ссылки на потоки и акцент на "ДРУГОЙ" поток.

35
ответ дан 29 November 2019 в 06:13
поделиться

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

0
ответ дан 29 November 2019 в 06:13
поделиться

Пожалуйста, НЕ блокируйте строковый объект. Это может привести к неожиданному поведению, например, к блокировкам в вашем приложении. В настоящее время вы блокируете пустую строку, что еще хуже. Вся сборка использует одну и ту же пустую строку. И что еще хуже: в качестве оптимизации CLR повторно использует строки в AppDomains. Блокировка строки означает, что вы, возможно, выполняете междоменную блокировку.

Используйте следующий код в качестве объекта блокировки:

private readonly static object obj = new object();

UPDATE

На самом деле, я думаю, можно с уверенностью сказать, что возможность блокировки на чем угодно - это главный недостаток дизайна .NET framework. Вместо этого им следовало бы создать некий SyncRoot герметичный класс и разрешить только оператору lock и Monitor.Enter принимать экземпляры SyncRoot. Это избавило бы нас от многих страданий. Я понимаю, откуда взялся этот недостаток; в Java есть такая же конструкция.

35
ответ дан 29 November 2019 в 06:13
поделиться

Как уже отметили другие, замок держится на нити и поэтому будет работать. Однако я хочу кое-что добавить к этому.

Джо Даффи, специалист по параллелизму из 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);
    }
}
9
ответ дан 29 November 2019 в 06:13
поделиться

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

6
ответ дан 29 November 2019 в 06:13
поделиться

Это не имеет ничего общего с блокировкой. Проверьте ваш код рекурсии. где находится пограничный случай для остановки рекурсии?

0
ответ дан 29 November 2019 в 06:13
поделиться

Если вы спрашиваете об исключении переполнения стека, то это потому, что там нет ничего, чтобы прервать рекурсию. Стековое пространство обычно составляет всего несколько 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);
}

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

1
ответ дан 29 November 2019 в 06:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: