Существуют ли зомби ... в .NET?

385

У меня была дискуссия с товарищем по команде о блокировке в .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 выполняет какую-то обработку, которая защищает потоки от воздействия системных компонентов, которые могут оказать такое влияние, но это чисто умозрительный и, вероятно, просто основанный на том факте, что я никогда не слышал о «потоках зомби» перед. Итак, я надеюсь, что я могу получить некоторую обратную связь по этому вопросу здесь:

  1. Есть ли более четкое определение «нити зомби», чем то, что я объяснил здесь?
  2. Могут ли зомби-потоки появляться в .NET? (Почему, почему нет?)
  3. Если применимо, как я могу форсировать создание потока зомби в .NET?
  4. Если применимо, как я могу использовать блокировку, не рискуя сценарием потока зомби в .NET?

Обновить

Я задал этот вопрос чуть более двух лет назад. Сегодня это произошло:

Объект находится в состоянии зомби.

smartcaveman
источник
8
Вы уверены, что ваш партнер не говорит о взаимоблокировке?
Андреас Нидермайр
10
@AndreasNiedermair - я знаю, что такое взаимоблокировка, и дело явно не в неправильном использовании этой терминологии. В разговоре упоминалось о взаимоблокировке, которая явно отличалась от «нити зомби». Для меня главное различие заключается в том, что мертвая блокировка имеет двустороннюю неразрешимую зависимость, тогда как поток зомби является односторонним и требует завершения процесса. Если вы не согласны и думаете, что есть лучший способ взглянуть на эти вещи, пожалуйста, объясните
smartcaveman
19
Я думаю, что термин "зомби" на самом деле происходит от фона UNIX, как в "процессе зомби", верно ??? В UNIX существует четкое определение «процесса зомби»: он описывает дочерний процесс, который завершился, но где родительский процесс дочернего процесса все еще должен «освободить» дочерний процесс (и его ресурсы), вызвав waitили waitpid. Дочерний процесс тогда называется "процессом зомби". Смотрите также howtogeek.com/119815
hogliux
9
Если часть вашей программы падает, оставляя программу в неопределенном состоянии, то, конечно, это может вызвать проблемы с остальной частью вашей программы. То же самое может произойти, если вы неправильно обрабатываете исключения в однопоточной программе. Проблема не в потоках, а в том, что у вас глобальное изменяемое состояние и вы неправильно обрабатываете неожиданное завершение потока. Ваш "действительно яркий" коллега на этом совершенно не работает.
Джим Мишель
9
«С тех пор как появились первые компьютеры, в машине всегда были призраки. Случайные сегменты кода, сгруппированные для формирования неожиданных протоколов ...»
Крис Лапланте,

Ответы:

242
  • Есть ли более четкое определение «нити зомби», чем то, что я объяснил здесь?

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

  • Могут ли зомби-потоки появляться в .NET? (Почему, почему нет?)
  • Если применимо, как я могу форсировать создание потока зомби в .NET?

Послушайте, я сделал один!

[DllImport("kernel32.dll")]
private static extern void ExitThread(uint dwExitCode);

static void Main(string[] args)
{
    new Thread(Target).Start();
    Console.ReadLine();
}

private static void Target()
{
    using (var file = File.Open("test.txt", FileMode.OpenOrCreate))
    {
        ExitThread(0);
    }
}

Эта программа запускает поток, Targetкоторый открывает файл, а затем немедленно убивает себя, используя ExitThread. Получившийся поток зомби никогда не выпустит дескриптор файла «test.txt», и поэтому файл будет оставаться открытым до завершения программы (вы можете проверить это с помощью Process Explorer или аналогичного). Дескриптор "test.txt" не будет выпущен до тех пор, пока не GC.Collectбудет вызван - оказывается, это даже сложнее, чем я думал, создать поток зомби, который пропускает дескрипторы)

  • Если применимо, как я могу использовать блокировку, не рискуя сценарием потока зомби в .NET?

Не делай то, что я только что сделал!

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

На самом деле на самом деле удивительно сложно создать поток зомби (мне пришлось P / Invoke в функцию, которая по сути говорит вам в документации не вызывать ее вне C). Например, следующий (ужасный) код фактически не создает поток зомби.

static void Main(string[] args)
{
    var thread = new Thread(Target);
    thread.Start();
    // Ugh, never call Abort...
    thread.Abort();
    Console.ReadLine();
}

private static void Target()
{
    // Ouch, open file which isn't closed...
    var file = File.Open("test.txt", FileMode.OpenOrCreate);
    while (true)
    {
        Thread.Sleep(1);
    }
    GC.KeepAlive(file);
}

Несмотря на некоторые довольно ужасные ошибки, дескриптор "test.txt" по-прежнему закрывается, как только Abortвызывается (как часть финализатора, для fileкоторого под прикрытием используется SafeFileHandle для переноса дескриптора файла)

Пример блокировки в ответе C.Evenhuis, вероятно, является самым простым способом не выпустить ресурс (в данном случае блокировку), когда поток завершается не странным образом, но это легко исправить, используя lockвместо этого оператор или положить релиз в finallyблоке.

Смотрите также

Джастин
источник
3
Я помню, когда я играл с сохранением материала в Excel с помощью фонового работника, я не выпускал все ресурсы все время (потому что я просто пропустил отладку и т. д.). в диспетчере задач я увидел около 50 процессов Excel. я создал zombieexcelprocesses?
Стефан
3
@Justin - +1 - Отличный ответ. Я немного скептически отношусь к вашему ExitThreadзвонку. Очевидно, это работает, но это больше похоже на хитрый трюк, чем на реалистичный сценарий. Одна из моих целей - научиться тому, чего не следует делать, чтобы случайно не создавать потоки зомби с помощью кода .NET. Я, вероятно, мог бы понять, что вызов кода C ++, который, как известно, вызывает эту проблему из кода .NET, даст желаемый эффект. Вы, очевидно, много знаете об этом. Известны ли вам какие-либо другие случаи (возможно, странные, но не настолько странные, чтобы никогда не происходить непреднамеренно) с таким же результатом?
smartcaveman
3
Таким образом, ответ «не в C # 4», верно? Если вам нужно выпрыгнуть из CLR, чтобы получить поток зомби, это не похоже на проблему .Net.
Гусдор
4
@ Стефан, я бы сказал, почти точно нет. Я сделал то, что вы описали много раз. Процессы Excel не являются зомби, поскольку они все еще работают, но не сразу доступны через обычный пользовательский интерфейс. Вы должны быть в состоянии получить их через вызовы GetObject . Set excelInstance = GetObject(, "Excel.Application")
Даниил
7
«Получившийся поток зомби никогда не освободит дескриптор файла« test.txt », поэтому файл останется открытым до завершения работы программы» неверно. Небольшое доказательство: `static void Main (string [] args) {new Thread (GcCollect) .Start (); новая тема (цель) .Start (); Console.ReadLine (); } private static void Target () {using (var file = File.Open ("test.txt", FileMode.OpenOrCreate)) {ExitThread (0); }} приватная статическая пустота GcCollect () {while (true) {Thread.Sleep (10000); GC.Collect (); }} `
Sinix
46

Я немного исправил свой ответ, но оставил исходный ниже для справки

Я впервые слышу о термине зомби, поэтому я предполагаю, что его определение таково:

Поток, который завершился без освобождения всех своих ресурсов

Поэтому, учитывая это определение, тогда да, вы можете сделать это в .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 может быть хорошим выбором.

Иерахмиил
источник
(1) Я не уверен, как выяснить, что происходит под капотом, когда я захожу в тупик externметодами. Если у вас есть предложение, я хотел бы услышать его. (2) Я бы согласился, что это можно сделать в .NET. Я хотел бы верить, что это возможно с блокировкой, но я еще не нашел удовлетворительного ответа, чтобы оправдать это сегодня
smartcaveman
1
@smartcaveman если то , что вы имеете в виду lockingэто lockключевое слово, то , возможно , не так как он упорядочивает выполнение. Чтобы максимизировать пропускную способность, вы должны использовать правильные конструкции в зависимости от характеристик вашего кода. Я бы сказал, что если вы можете написать надежный код в c / c ++ / java / что угодно, используя pthreads / boost / thread pool / что угодно, то вы можете написать его и в C #. Но если вы не можете написать надежный код на любом языке, используя любую библиотеку, то я сомневаюсь, что написание на C # будет другим.
Джерахмил
@smartcaveman, что касается выяснения того, что скрывается под капотом, Google помогает кучу, и если ваша проблема слишком экзотическая, чтобы найти ее в Интернете, рефлектор очень удобен. Но для классов потоков я нахожу документацию MSDN очень полезной. И многие из них являются просто обертками для тех же системных вызовов, которые вы используете в C anway.
Джерахмил
1
@ smartcaveman совсем нет, я не это имел ввиду. Извините, если так натолкнулся. Я хотел сказать, что не следует слишком быстро списывать .NET при написании критически важных приложений. Конечно, вы могли бы делать вещи, которые, возможно, намного хуже, чем потоки зомби (которые, я думаю, являются просто потоками, которые не освобождают неуправляемые ресурсы, что может полностью происходить на других языках: stackoverflow.com/questions/14268080/… ), но Опять же, это не означает, что .NET не является жизнеспособным решением.
Джерахмил
2
Я не думаю, что .NET вообще плохая технология. Это на самом деле моя основная структура разработки. Но из-за этого, я думаю, важно понимать недостатки, к которым он подвержен. По сути, я выделяю .NET, но потому что мне это нравится, а не потому, что мне это не нравится. (И не беспокойся, братан, я + 1-й ты)
smartcaveman
26

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

Бессмертный Синий отметил, что в .NET 2.0 и выше finallyблоки не защищены от прерываний потоков. И как прокомментировал Andreas Niedermair, это может быть не настоящий поток зомби, но в следующем примере показано, как прерывание потока может вызвать проблемы:

class Program
{
    static readonly object _lock = new object();

    static void Main(string[] args)
    {
        Thread thread = new Thread(new ThreadStart(Zombie));
        thread.Start();
        Thread.Sleep(500);
        thread.Abort();

        Monitor.Enter(_lock);
        Console.WriteLine("Main entered");
        Console.ReadKey();
    }

    static void Zombie()
    {
        Monitor.Enter(_lock);
        Console.WriteLine("Zombie entered");
        Thread.Sleep(1000);
        Monitor.Exit(_lock);
        Console.WriteLine("Zombie exited");
    }
}

Однако при использовании lock() { }блока finallyвсе равно будет выполняться, когда a ThreadAbortExceptionзапускается таким образом.

Следующая информация, как выясняется, действительна только для .NET 1 и .NET 1.1:

Если внутри lock() { }блока возникает другое исключение, и оно ThreadAbortExceptionприходит именно тогда, когда finallyблок должен быть запущен, блокировка не снимается. Как вы упомянули, lock() { }блок компилируется как:

finally 
{
    if (lockWasTaken) 
        Monitor.Exit(temp); 
}

Если другой поток вызывает Thread.Abort()внутри сгенерированного finallyблока, блокировка может быть не снята.

C.Evenhuis
источник
2
Вы говорите, lock()но я не вижу никакого использования этого ... так - как это связано? это «неправильное» использование Monitor.Enterи Monitor.Exit(без использования tryи finally)
Andreas Niedermair
4
Я бы не назвал это потоком зомби - это просто неправильное использование Monitor.Enterи Monitor.Exitбез правильного использования tryи finally- в любом случае, ваш сценарий заблокирует другие потоки, которые могут зависать _lock, поэтому существует сценарий взаимоблокировки - не обязательно поток зомби ... Кроме того, вы не снимаете блокировку в Main... но, эй ... возможно, ОП блокирует блокировку вместо зомби-потоков :)
Andreas Niedermair
1
@AndreasNiedermair, возможно, определение нити зомби не совсем то, что я думал. Возможно, мы можем назвать это «потоком, который завершил выполнение, но не освободил все ресурсы». Соблазн удалить и выполнить домашнее задание, но сохранил ответ на вопрос о сходстве со сценарием ОП.
C.Evenhuis
2
здесь без обид! на самом деле твой ответ только что заставил меня задуматься об этом :), поскольку ОП явно говорит о блокировках, которые не снимаются, я полагаю, что его коллега говорил о взаимоблокировке - потому что блокировка - это не реальный ресурс, связанный с потоком ( это общедоступно ... nöna) - и, следовательно, любой "зомби" нити можно избежать, придерживаясь передового опыта и используя lockили правильно try/ finally-usage
Andreas Niedermair
1
@ C.Evenhuis - я не уверен, что мое определение является точным. Часть того, почему я задаю вопрос, состоит в том, чтобы прояснить это. Я думаю, что концепция часто упоминается в C / C ++
smartcaveman
24

Речь идет не о потоках Zombie, но в книге Effective C # есть раздел, посвященный реализации IDisposable (пункт 17), в котором говорится об объектах Zombie, которые, как мне показалось, могут вас заинтересовать.

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

Это дает пример, аналогичный приведенному ниже:

internal class Zombie
{
    private static readonly List<Zombie> _undead = new List<Zombie>();

    ~Zombie()
    {
        _undead.Add(this);
    }
}

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

Более полный пример приведен ниже. К тому времени, когда цикл foreach достигнут, у вас есть 150 объектов в списке Нежити, каждый из которых содержит изображение, но изображение уже получено с помощью GC, и вы получите исключение, если попытаетесь его использовать. В этом примере я получаю ArgumentException (параметр недействителен), когда я пытаюсь что-либо сделать с изображением, пытаюсь ли я сохранить его или даже просматривать размеры, такие как высота и ширина:

class Program
{
    static void Main(string[] args)
    {
        for (var i = 0; i < 150; i++)
        {
            CreateImage();
        }

        GC.Collect();

        //Something to do while the GC runs
        FindPrimeNumber(1000000);

        foreach (var zombie in Zombie.Undead)
        {
            //object is still accessable, image isn't
            zombie.Image.Save(@"C:\temp\x.png");
        }

        Console.ReadLine();
    }

    //Borrowed from here
    //http://stackoverflow.com/a/13001749/969613
    public static long FindPrimeNumber(int n)
    {
        int count = 0;
        long a = 2;
        while (count < n)
        {
            long b = 2;
            int prime = 1;// to check if found a prime
            while (b * b <= a)
            {
                if (a % b == 0)
                {
                    prime = 0;
                    break;
                }
                b++;
            }
            if (prime > 0)
                count++;
            a++;
        }
        return (--a);
    }

    private static void CreateImage()
    {
        var zombie = new Zombie(new Bitmap(@"C:\temp\a.png"));
        zombie.Image.Save(@"C:\temp\b.png");
    }
}

internal class Zombie
{
    public static readonly List<Zombie> Undead = new List<Zombie>();

    public Zombie(Image image)
    {
        Image = image;
    }

    public Image Image { get; private set; }

    ~Zombie()
    {
        Undead.Add(this);
    }
}

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

JMK
источник
1
Это интересно. Это _undeadдолжно быть статичным?
Smartcaveman
1
Итак, я попробовал это, с некоторой распечаткой «разрушенного» объекта. Это относится к нему как к нормальному объекту. Есть ли что-то проблематичное в этом?
Smartcaveman
1
Я обновил свой ответ с примером, который, надеюсь, демонстрирует проблему немного более четко.
JMK
1
Я не знаю, почему ты получил отрицательный голос. Я нашел это полезным. Вот вопрос - какое исключение вы собираетесь получить? Я не ожидаю, что это произойдет NullReferenceException, потому что я чувствую, что недостающее должно быть больше привязано к машине, чем к приложению. Это правильно?
smartcaveman
4
Конечно IDisposable, проблема не столько в этом. Я беспокоюсь о финализаторах (деструкторах), но просто из-за IDisposableтого, что объект не попадет в очередь финализаторов и не рискует этим сценарием зомби. Это предупреждение касается финализаторов, и они могут вызывать методы Dispose. Есть примеры, где IDisposableиспользуется в типах без финализаторов. Смысл должен быть в очистке ресурсов, но это может быть нетривиальная очистка ресурсов. RX правильно использует IDisposableдля очистки подписок и может вызывать другие нисходящие ресурсы. (Кстати, я не понизил голос) ...
Джеймс Уорлд,
21

На критических системах под большой нагрузкой написание кода без блокировки лучше в первую очередь из-за повышения производительности. Посмотрите на такие вещи, как LMAX и на то, как он использует «механическую симпатию» для больших обсуждений этого. Хотя беспокоиться о зомби темы? Я думаю, что это крайний случай, это просто ошибка, которую нужно устранить, а не достаточно веская причина, чтобы ее не использовать lock.

Похоже, ваш друг просто придирчив и выставляет напоказ свое знание неясной экзотической терминологии для меня! За все время работы над лабораториями производительности в Microsoft UK я никогда не сталкивался с подобной проблемой в .NET.

Мир джеймса
источник
2
Я думаю, что различные наборы опыта делают нас параноиками по поводу различных ошибок. Он признал, что это крайний крайний случай, когда объясняет его, но если это действительно крайний случай, а не
случайный
1
Справедливо. Я просто не хотел бы, чтобы вы были непропорционально обеспокоены этим lockутверждением!
Джеймс Уорлд,
1
Я бы гораздо меньше волновался, если бы имел более глубокое понимание этого вопроса.
smartcaveman
4
Я проголосовал, потому что вы пытаетесь избавиться от некоторого потока-FUD, которого слишком много. Lock-FUD потребует больше работы, чтобы избавиться от :) Я просто съеживаюсь, когда разработчики берут неограниченную спин-блокировку (для производительности), чтобы они могли скопировать 50К данных в широкую очередь.
Мартин Джеймс
3
Да, как в общем, так и в частном. Написание правильного кода без блокировок - задача не менее сложная, чем программирование. По моему опыту в подавляющем большинстве случаев, большие жирные конечно- lockмелкие (относительно) легкие для понимания блоки просто хороши. Я бы не стал вводить сложности для оптимизации производительности, пока вы не поймете, что это необходимо.
Мир Джеймса
3

1. Есть ли более четкое определение «нити зомби», чем то, что я объяснил здесь?

Я согласен с тем, что существуют «потоки зомби», это термин для обозначения того, что происходит с потоками, которые остаются с ресурсами, от которых они не отпускают и все же не умирают полностью, отсюда и название «зомби», так что объяснение этого реферала довольно точно на деньги!

2. Могут ли темы зомби возникать в .NET? (Почему, почему нет?)

Да, они могут произойти. Это ссылка, и на самом деле Windows упоминается как «зомби»: MSDN использует слово «зомби» для мертвых процессов / потоков

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

И да, как @KevinPanko правильно упомянул в комментариях, «Потоки зомби» происходят из Unix, поэтому они используются в XCode-ObjectiveC и называются «NSZombie» и используются для отладки. Он ведет себя примерно так же ... единственное отличие состоит в том, что объект, который должен был умереть, становится "ZombieObject" для отладки вместо "Zombie Thread", что может быть потенциальной проблемой в вашем коде.

SH
источник
1
Но то, как MSDN использует зомби , сильно отличается от того, как этот вопрос использует его.
Бен Фойгт
1
О да, я согласен, но я подчеркивал, что даже MSDN называет потоки Zombie Threads, когда они мертвы. и что они на самом деле могут произойти.
SH
4
Конечно, но это относится к некоторому другому коду, который все еще держит дескриптор потока, а не к потоку, который удерживает дескрипторы ресурсов при выходе. Ваше первое предложение, согласное с определением в вопросе, является тем, что не так.
Бен Фойгт
1
Ааа, я понимаю вашу точку зрения. Если честно, я даже этого не заметил. Я сосредоточил внимание на том, что определение существует, независимо от того, как оно происходит. Помните, что определение существует из-за того, что происходит с потоком, а не как это делается.
SH
0

Я могу сделать нити зомби достаточно легко.

var zombies = new List<Thread>();
while(true)
{
    var th = new Thread(()=>{});
    th.Start();
    zombies.Add(th);
}

Это приводит к утечке нитей ручки (для Join()). Это просто очередная утечка памяти, насколько мы обеспокоены в управляемом мире.

Теперь, если убить нить так, чтобы она на самом деле удерживала замки, это боль в тылу, но это возможно. Другой парень ExitThread()делает работу. Как он обнаружил, дескриптор файла был очищен gc, но lockвокруг объекта не будет. Но зачем ты это делаешь?

Джошуа
источник