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




multithreading load (5)

У меня была дискуссия с товарищем по команде о блокировке в .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?

Обновить

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


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

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

2.Can-зомби-потоки встречаются на .NET? (Почему, почему нет?)

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

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

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


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

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


Речь идет не о потоках 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 объектов в списке Undead, каждый из которых содержит изображение, но изображение было GC'd, и вы получаете исключение, если пытаетесь его использовать. В этом примере я получаю 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://.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, и мне напомнили об этом и подумали, что другие могут найти это интересным!


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

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

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

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


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

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

  • Могут ли зомби-потоки встречаться на .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 и поэтому файл остается открытым до тех пор, пока программа не завершится (вы можете проверить с помощью проводника процессов или аналогичного). Ручка «test.txt» не будет выпущена до тех пор, пока не будет вызван GC.Collect - оказалось, что это еще сложнее, чем я думал создать зомби-поток, который теряет ручки)

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

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

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

На самом деле его на самом деле удивительно сложно создать зомби-поток (мне пришлось P / Invoke в функцию, которая esentially говорит вам в документации, чтобы не вызывать ее вне 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 .

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







zombie-process