Подтвердить что ты не робот

Использование блокировки (obj) внутри рекурсивного вызова

В соответствии с моим пониманием блокировка не выводится до тех пор, пока среда выполнения не завершит блок кода блокировки (obj) (поскольку, когда блок завершает работу, он вызывает Monitor.Exit(obj).

При таком понимании я не могу понять причину поведения следующего кода:

private static string obj = "";
        private static void RecurseSome(int number)
        {
            Console.WriteLine(number);
            lock (obj)
            {
                RecurseSome(++number);
            }
        }

// Вызов: RecurseSome(0)

// Выход: 0 1 2 3...... Qaru exception

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

4b9b3361

Ответ 1

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

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

мс-помощь://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_csref/html/656da1a4-707e-4ef6-9c6e-6d13b646af42.htm

Или MSDN: http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

говорится:

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

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

Ответ 2

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

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

private readonly static object obj = new object();

ОБНОВЛЕНИЕ

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

Ответ 3

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

Джо Даффи, специалист из concurrency от Microsoft, имеет несколько правил проектирования о concurrency. В одном из его правил дизайна указано:

9. Избегайте рекурсии блокировки в вашем дизайне. Использовать нерекурсивную блокировку где возможно.

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

(источник)

Чтобы предотвратить рекурсивную блокировку, перепишите код на следующее:

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

Ответ 4

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

Ответ 5

Если вы спрашиваете об исключении - это потому, что там нет ничего, что могло бы выйти из рекурсии. Обычно пространство стека составляет всего несколько К, и вы достаточно быстро исчерпаете пространство.

Теперь блокировка в этом случае может использоваться для сериализации вывода вызова, так что если вы вызовите 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);
}

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

Ответ 6

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

Ответ 7

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