Как найти тупик и предотвратить его в С# - программирование
Подтвердить что ты не робот

Как найти тупик и предотвратить его в С#

У меня было интервью всего 5 минут назад, я не ответил на 3 вопроса, мог ли кто-нибудь помочь мне.

Вопрос:

Как искать сценарии взаимоблокировки в многопоточном приложении и предотвращать его?

Ответ, который я дал:

Я дал определение тупика и блокировки, мьютекса, монитора, семафора. Он сказал мне, что это инструменты, но как искать сценарий тупиковой ситуации, потому что, когда мы используем эти инструменты вслепую, это стоит того, что он сказал: (

Пожалуйста, помогите мне понять это.

4b9b3361

Ответ 1

Инструменты анализа производительности также могут быть полезны при выявлении взаимоблокировок, среди прочего. Этот вопрос поможет понять эту тему: инструмент анализа С#/.NET для поиска условий гонки/взаимоблокировок.

Визуальный анализ кода и правильное использование блокировок также полезен (вы должны быть в состоянии обнаружить потенциальные проблемы в коде при его проверке), но он может быть очень сложным для сложных приложений. Иногда взаимоблокировки видны только при запуске кода, а не просто при проверке кода.

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

Блокировка чего-либо только ради блокировки влияет на производительность, поскольку потоки ожидают друг друга. Вы должны проанализировать рабочий процесс, чтобы определить, что действительно нужно блокировать, когда, с каким типом блокировки (простой lock или, может быть, ReaderWriterLockSlim). Существует много типичных способов предотвращения тупиковых ситуаций.

Например, при использовании ReaderWriterLockSlim вы можете использовать тайм-аут для предотвращения взаимных блокировок (если вы слишком долго ждете, вы отменяете блокировку)

if (cacheLock.TryEnterWriteLock(timeout))
{
...
}

И вы должны быть в состоянии предложить такие тайм-ауты.

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

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

Ответ 2

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

Взаимная блокировка возникает, когда каждый поток (минимум два) пытается получить блокировку для ресурса, уже заблокированного другим. Поток 1, заблокированный на Ресурсах 1, пытается получить блокировку на Ресурсе 2. В то же время Поток 2 имеет блокировку на Ресурсе 2, и он пытается получить блокировку на Ресурсе 1. Два потока никогда не снимают свои блокировки, следовательно тупик происходит.

Самый простой способ избежать тупика - использовать значение тайм-аута. Класс Monitor (system.Threading.Monitor) может установить время ожидания при получении блокировки.

Пример

try{
    if(Monitor.TryEnter(this, 500))
    {
        // critical section
    }
}
catch (Exception ex)
{

}
finally
{
    Monitor.Exit();
}

Подробнее

Ответ 3

Я думаю, что собеседование задает вам трюк. Если бы вы могли использовать статический анализ, чтобы предотвратить тупик... никто бы не зашел в тупик!

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

void func(){
    lock(_lock){
        func2();
     }
}

Не совсем понятно, что делает func2. Возможно, он отправляет событие в тот же поток, что означает, что это событие все еще является частью критического раздела. Может быть, тогда он запирает другой замок. Может быть, он отправляется в threadpool и больше не возвращается, потому что теперь он в другой теме! В таких местах вы можете начать видеть сценарии взаимоблокировки: когда у вас несколько мест размещения без реентера.

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

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

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

Я не согласен с вашим интервью, что блокировки всегда добавляют огромную проблему с производительностью. Неисправные блокировки/мьютексы/и т.д. Первый тест в качестве спин-блокировки перед передачей ОС и шпиндельных замков дешевый.

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

Ответ 4

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

Mutex m; // similar overloads exist for all locking primitives
if (!m.WaitOne(TimeSpan.FromSeconds(30)))
    throw new Exception("Potential deadlock detected.");

Когда WaitOne возвращает false, он будет ждать 30 секунд, и блокировка по-прежнему не будет выпущена. Если вы знаете, что все заблокированные операции должны выполняться в течение миллисекунд (если нет, то просто увеличивайте время ожидания), это очень хороший признак того, что что-то пошло плохо.