it-swarm.com.ru

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

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

Вопрос:

Как искать сценарии тупиковой ситуации в функции многопоточного приложения и предотвращать это? 

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

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

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

15
Learner

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

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

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

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

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

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

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

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

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

4
Coral Doe

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

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

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

Пример

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

}
finally
{
    Monitor.Exit();
}

Прочитайте больше

6
Gaʀʀʏ

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

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

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

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

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

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

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

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

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

4
devshorts

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

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

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

0
Knaģis