У меня была дискуссия с товарищем по команде о блокировке в .NET. Он действительно умный парень с обширным опытом как в программировании на низком, так и на высоком уровне, но его опыт в программировании на низком уровне намного превосходит мой. Во всяком случае, он утверждал, что следует избегать блокировки .NET в критических системах, которые, как ожидается, будут находиться под большой нагрузкой, если это вообще возможно, чтобы избежать, по общему признанию, малой вероятности сбоя системы «потоком зомби». Я обычно использую блокировку, и я не знал, что такое «нить зомби», поэтому я спросил. Из его объяснения у меня сложилось впечатление, что нить зомби - это нить, которая завершилась, но каким-то образом все еще держит некоторые ресурсы. Он привел пример того, как поток зомби может сломать систему, когда поток начинает некоторую процедуру после блокировки какого-либо объекта, и затем в какой-то момент завершается, прежде чем блокировка может быть снята. Эта ситуация может привести к сбою системы, поскольку в конечном итоге попытки выполнить этот метод приведут к тому, что все потоки будут ожидать доступа к объекту, который никогда не будет возвращен, поскольку поток, использующий заблокированный объект, мертв.
Я думаю, что понял суть этого, но если я не в базе, пожалуйста, дайте мне знать. Концепция имела смысл для меня. Я не был полностью убежден, что это реальный сценарий, который может произойти в .NET. Раньше я никогда не слышал о «зомби», но я признаю, что программисты, которые глубоко работали на более низких уровнях, как правило, имеют более глубокое понимание основ вычислительной техники (например, многопоточности). Однако я определенно вижу значение в блокировке, и я видел, как многие программисты мирового класса используют блокировку. У меня также есть ограниченные возможности оценить это для себя, потому что я знаю, что это lock(obj)
утверждение является просто синтаксическим сахаром для:
bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }
а потому Monitor.Enter
и Monitor.Exit
помечены extern
. Представляется вероятным, что .NET выполняет какую-то обработку, которая защищает потоки от воздействия системных компонентов, которые могут оказать такое влияние, но это чисто умозрительный и, вероятно, просто основанный на том факте, что я никогда не слышал о «потоках зомби» перед. Итак, я надеюсь, что я могу получить некоторую обратную связь по этому вопросу здесь:
- Есть ли более четкое определение «нити зомби», чем то, что я объяснил здесь?
- Могут ли зомби-потоки появляться в .NET? (Почему, почему нет?)
- Если применимо, как я могу форсировать создание потока зомби в .NET?
- Если применимо, как я могу использовать блокировку, не рискуя сценарием потока зомби в .NET?
Обновить
Я задал этот вопрос чуть более двух лет назад. Сегодня это произошло:
источник
wait
илиwaitpid
. Дочерний процесс тогда называется "процессом зомби". Смотрите также howtogeek.com/119815Ответы:
Мне кажется, это довольно хорошее объяснение - поток, который завершился (и поэтому не может больше освобождать какие-либо ресурсы), но чьи ресурсы (например, дескрипторы) все еще существуют и (потенциально) вызывают проблемы.
Послушайте, я сделал один!
Эта программа запускает поток,
Target
который открывает файл, а затем немедленно убивает себя, используяExitThread
.Получившийся поток зомби никогда не выпустит дескриптор файла «test.txt», и поэтому файл будет оставаться открытым до завершения программы (вы можете проверить это с помощью Process Explorer или аналогичного).Дескриптор "test.txt" не будет выпущен до тех пор, пока неGC.Collect
будет вызван - оказывается, это даже сложнее, чем я думал, создать поток зомби, который пропускает дескрипторы)Не делай то, что я только что сделал!
До тех пор, пока ваш код корректно очищается после себя (используйте Безопасные дескрипторы или эквивалентные классы при работе с неуправляемыми ресурсами) и до тех пор, пока вы не стараетесь убивать потоки странными и чудесными способами (самый безопасный способ - это просто чтобы не убивать нити - пусть прекратить себя нормально, или через исключение в случае необходимости), единственным способом , что вы будете иметь что - то похожее на зомби нити, если что - то пошло очень неправильно (например , что - то пойдет не так в CLR).
На самом деле на самом деле удивительно сложно создать поток зомби (мне пришлось P / Invoke в функцию, которая по сути говорит вам в документации не вызывать ее вне C). Например, следующий (ужасный) код фактически не создает поток зомби.
Несмотря на некоторые довольно ужасные ошибки, дескриптор "test.txt" по-прежнему закрывается, как только
Abort
вызывается (как часть финализатора, дляfile
которого под прикрытием используется SafeFileHandle для переноса дескриптора файла)Пример блокировки в ответе C.Evenhuis, вероятно, является самым простым способом не выпустить ресурс (в данном случае блокировку), когда поток завершается не странным образом, но это легко исправить, используя
lock
вместо этого оператор или положить релиз вfinally
блоке.Смотрите также
lock
ключевого слова (но только в .Net 3.5 и более ранних версиях)источник
ExitThread
звонку. Очевидно, это работает, но это больше похоже на хитрый трюк, чем на реалистичный сценарий. Одна из моих целей - научиться тому, чего не следует делать, чтобы случайно не создавать потоки зомби с помощью кода .NET. Я, вероятно, мог бы понять, что вызов кода C ++, который, как известно, вызывает эту проблему из кода .NET, даст желаемый эффект. Вы, очевидно, много знаете об этом. Известны ли вам какие-либо другие случаи (возможно, странные, но не настолько странные, чтобы никогда не происходить непреднамеренно) с таким же результатом?Set excelInstance = GetObject(, "Excel.Application")
Я немного исправил свой ответ, но оставил исходный ниже для справки
Я впервые слышу о термине зомби, поэтому я предполагаю, что его определение таково:
Поток, который завершился без освобождения всех своих ресурсов
Поэтому, учитывая это определение, тогда да, вы можете сделать это в .NET, как и в других языках (C / C ++, Java).
Тем не менее , я не считаю это хорошей причиной, чтобы не писать многопоточный критически важный код в .NET. Могут быть и другие причины, чтобы отказаться от .NET, но списание .NET только потому, что у вас могут быть потоки зомби, для меня не имеет смысла. Зомби-потоки возможны в C / C ++ (я бы даже сказал, что в C гораздо проще запутаться), а многие критически важные многопоточные приложения находятся в C / C ++ (торговля большими объемами, базы данных и т.д.).
Заключение Если вы находитесь в процессе выбора языка для использования, то я предлагаю вам принять во внимание общую картину: производительность, командные навыки, расписание, интеграция с существующими приложениями и т. Д. Конечно, темы зомби - это то, о чем вы должны подумать , но так как на самом деле очень трудно совершить эту ошибку в .NET по сравнению с другими языками, такими как C, я думаю, что эта проблема будет омрачена другими вещами, такими как упомянутые выше. Удачи!
Оригинальный ответ Zombies † может существовать, если вы не пишете правильный многопоточный код. То же самое верно и для других языков, таких как C / C ++ и Java. Но это не причина не писать многопоточный код в .NET.
И, как и с любым другим языком, узнайте цену, прежде чем что-то использовать. Это также помогает узнать, что происходит под капотом, чтобы вы могли предвидеть любые потенциальные проблемы.
Надежный код для критически важных систем написать нелегко, на каком бы языке вы ни находились. Но я уверен, что в .NET это невозможно сделать правильно. Кроме того, AFAIK. Многопоточность .NET не сильно отличается от многопоточности в C / C ++, она использует (или создается из) одни и те же системные вызовы, за исключением некоторых специфических конструкций .net (например, облегченных версий RWL и классов событий).
† впервые я слышал о термине зомби, но, исходя из вашего описания, ваш коллега, вероятно, имел в виду поток, который завершился без освобождения всех ресурсов. Это может привести к взаимоблокировке, утечке памяти или другим нежелательным побочным эффектам. Это, очевидно, нежелательно, но выделять .NET из-за этой возможности , вероятно, не очень хорошая идея, поскольку это возможно и в других языках. Я бы даже сказал, что в C / C ++ легче запутаться, чем в .NET (особенно в C, где у вас нет RAII), но многие критически важные приложения написаны на C / C ++, верно? Так что это действительно зависит от ваших индивидуальных обстоятельств. Если вы хотите извлечь из приложения каждую унцию скорости и хотите максимально приблизиться к голому металлу, тогда .NET можетне будет лучшим решением. Если у вас ограниченный бюджет и вы много взаимодействуете с веб-сервисами / существующими библиотеками .net / и т. Д., Тогда .NET может быть хорошим выбором.
источник
extern
методами. Если у вас есть предложение, я хотел бы услышать его. (2) Я бы согласился, что это можно сделать в .NET. Я хотел бы верить, что это возможно с блокировкой, но я еще не нашел удовлетворительного ответа, чтобы оправдать это сегодняlocking
этоlock
ключевое слово, то , возможно , не так как он упорядочивает выполнение. Чтобы максимизировать пропускную способность, вы должны использовать правильные конструкции в зависимости от характеристик вашего кода. Я бы сказал, что если вы можете написать надежный код в c / c ++ / java / что угодно, используя pthreads / boost / thread pool / что угодно, то вы можете написать его и в C #. Но если вы не можете написать надежный код на любом языке, используя любую библиотеку, то я сомневаюсь, что написание на C # будет другим.Прямо сейчас большая часть моего ответа была исправлена комментариями ниже. Я не буду удалять ответ,
потому что мне нужны очки репутации,потому что информация в комментариях может быть полезна для читателей.Бессмертный Синий отметил, что в .NET 2.0 и выше
finally
блоки не защищены от прерываний потоков. И как прокомментировал Andreas Niedermair, это может быть не настоящий поток зомби, но в следующем примере показано, как прерывание потока может вызвать проблемы:Однако при использовании
lock() { }
блокаfinally
все равно будет выполняться, когда aThreadAbortException
запускается таким образом.Следующая информация, как выясняется, действительна только для .NET 1 и .NET 1.1:
Если внутри
lock() { }
блока возникает другое исключение, и оноThreadAbortException
приходит именно тогда, когдаfinally
блок должен быть запущен, блокировка не снимается. Как вы упомянули,lock() { }
блок компилируется как:Если другой поток вызывает
Thread.Abort()
внутри сгенерированногоfinally
блока, блокировка может быть не снята.источник
lock()
но я не вижу никакого использования этого ... так - как это связано? это «неправильное» использованиеMonitor.Enter
иMonitor.Exit
(без использованияtry
иfinally
)Monitor.Enter
иMonitor.Exit
без правильного использованияtry
иfinally
- в любом случае, ваш сценарий заблокирует другие потоки, которые могут зависать_lock
, поэтому существует сценарий взаимоблокировки - не обязательно поток зомби ... Кроме того, вы не снимаете блокировку вMain
... но, эй ... возможно, ОП блокирует блокировку вместо зомби-потоков :)lock
или правильноtry
/finally
-usageРечь идет не о потоках Zombie, но в книге Effective C # есть раздел, посвященный реализации IDisposable (пункт 17), в котором говорится об объектах Zombie, которые, как мне показалось, могут вас заинтересовать.
Я рекомендую прочитать саму книгу, но суть в том, что если у вас есть класс, реализующий IDisposable или содержащий Desctructor, единственное, что вам нужно сделать, это освободить ресурсы. Если вы делаете здесь другие вещи, то есть вероятность, что объект не будет собирать мусор, но также не будет доступен в любом случае.
Это дает пример, аналогичный приведенному ниже:
Когда вызывается деструктор для этого объекта, ссылка на себя помещается в глобальный список, что означает, что он остается живым и хранится в памяти на протяжении всей жизни программы, но недоступен. Это может означать, что ресурсы (особенно неуправляемые ресурсы) могут быть освобождены не полностью, что может вызвать всевозможные потенциальные проблемы.
Более полный пример приведен ниже. К тому времени, когда цикл foreach достигнут, у вас есть 150 объектов в списке Нежити, каждый из которых содержит изображение, но изображение уже получено с помощью GC, и вы получите исключение, если попытаетесь его использовать. В этом примере я получаю ArgumentException (параметр недействителен), когда я пытаюсь что-либо сделать с изображением, пытаюсь ли я сохранить его или даже просматривать размеры, такие как высота и ширина:
Опять же, я знаю, что вы спрашивали, в частности, о темах зомби, но заголовок вопроса о зомби в .net, и мне напомнили об этом, и я подумал, что другие могут найти это интересным!
источник
_undead
должно быть статичным?NullReferenceException
, потому что я чувствую, что недостающее должно быть больше привязано к машине, чем к приложению. Это правильно?IDisposable
, проблема не столько в этом. Я беспокоюсь о финализаторах (деструкторах), но просто из-заIDisposable
того, что объект не попадет в очередь финализаторов и не рискует этим сценарием зомби. Это предупреждение касается финализаторов, и они могут вызывать методы Dispose. Есть примеры, гдеIDisposable
используется в типах без финализаторов. Смысл должен быть в очистке ресурсов, но это может быть нетривиальная очистка ресурсов. RX правильно используетIDisposable
для очистки подписок и может вызывать другие нисходящие ресурсы. (Кстати, я не понизил голос) ...На критических системах под большой нагрузкой написание кода без блокировки лучше в первую очередь из-за повышения производительности. Посмотрите на такие вещи, как LMAX и на то, как он использует «механическую симпатию» для больших обсуждений этого. Хотя беспокоиться о зомби темы? Я думаю, что это крайний случай, это просто ошибка, которую нужно устранить, а не достаточно веская причина, чтобы ее не использовать
lock
.Похоже, ваш друг просто придирчив и выставляет напоказ свое знание неясной экзотической терминологии для меня! За все время работы над лабораториями производительности в Microsoft UK я никогда не сталкивался с подобной проблемой в .NET.
источник
lock
утверждением!lock
мелкие (относительно) легкие для понимания блоки просто хороши. Я бы не стал вводить сложности для оптимизации производительности, пока вы не поймете, что это необходимо.Я согласен с тем, что существуют «потоки зомби», это термин для обозначения того, что происходит с потоками, которые остаются с ресурсами, от которых они не отпускают и все же не умирают полностью, отсюда и название «зомби», так что объяснение этого реферала довольно точно на деньги!
Да, они могут произойти. Это ссылка, и на самом деле Windows упоминается как «зомби»: MSDN использует слово «зомби» для мертвых процессов / потоков
Часто случается, что это другая история, и она зависит от ваших методов и практик кодирования, а для вас, которые любят блокировку потоков, и какое-то время делали это, я даже не стал бы беспокоиться о том, что такой сценарий случится с вами.
И да, как @KevinPanko правильно упомянул в комментариях, «Потоки зомби» происходят из Unix, поэтому они используются в XCode-ObjectiveC и называются «NSZombie» и используются для отладки. Он ведет себя примерно так же ... единственное отличие состоит в том, что объект, который должен был умереть, становится "ZombieObject" для отладки вместо "Zombie Thread", что может быть потенциальной проблемой в вашем коде.
источник
Я могу сделать нити зомби достаточно легко.
Это приводит к утечке нитей ручки (для
Join()
). Это просто очередная утечка памяти, насколько мы обеспокоены в управляемом мире.Теперь, если убить нить так, чтобы она на самом деле удерживала замки, это боль в тылу, но это возможно. Другой парень
ExitThread()
делает работу. Как он обнаружил, дескриптор файла был очищен gc, ноlock
вокруг объекта не будет. Но зачем ты это делаешь?источник