c# - vida - existen zombies en el mundo
¿Existen zombies... en.NET? (7)
1. ¿Existe una definición más clara de "hilo zombi" de lo que he explicado aquí?
Estoy de acuerdo en que existen "Temas de Zombies", es un término para referirse a lo que sucede con los Temas que se quedan con recursos que no abandonan y que no mueren por completo, de ahí el nombre de "zombie", por lo que ¡La explicación de esta referencia es bastante correcta en el dinero!
2. ¿Pueden aparecer hilos de zombis en .NET? (¿Por qué por qué no?)
Sí pueden ocurrir. Es una referencia, y en realidad se refiere a Windows como "zombie": MSDN usa la palabra "Zombie" para procesos / subprocesos muertos
Suceder con frecuencia es otra historia, y depende de tus técnicas y prácticas de codificación, ya que para ti, como Thread Locking y lo has hecho durante un tiempo, ni siquiera me preocuparía que te pasara esa situación.
Y sí, como @KevinPanko mencionó correctamente en los comentarios, "Zombie Threads" provienen de Unix, por lo que se utilizan en XCode-ObjectiveC y se denominan "NSZombie" y se usan para la depuración. Se comporta de la misma manera ... la única diferencia es que un objeto que debería haber muerto se convierte en un "ZombieObject" para la depuración en lugar del "Zombie Thread", que podría ser un problema potencial en su código.
Estaba teniendo una discusión con un compañero de equipo sobre el bloqueo en .NET. Es un tipo realmente brillante con una amplia experiencia tanto en la programación de nivel inferior como en la de nivel superior, pero su experiencia con la programación de nivel inferior es muy superior a la mía. De todos modos, argumentó que el bloqueo de .NET debería evitarse en los sistemas críticos que se espera que estén bajo una carga pesada, si es posible, para evitar la pequeña posibilidad de que un "hilo zombie" se estrelle contra un sistema. Rutinariamente uso el bloqueo y no sabía qué era un "hilo zombi", así que pregunté. La impresión que obtuve de su explicación es que un hilo zombie es un hilo que ha terminado pero que de alguna manera aún mantiene algunos recursos. Un ejemplo que dio de cómo un hilo zombie podría romper un sistema era que un hilo inicia algún procedimiento después de bloquear algún objeto, y luego termina en algún punto antes de que se pueda liberar el bloqueo. Esta situación tiene el potencial de bloquear el sistema, ya que, eventualmente, los intentos de ejecutar ese método darán como resultado que todos los subprocesos esperen el acceso a un objeto que nunca se devolverá, porque el subproceso que usa el objeto bloqueado está muerto.
Creo que entendí todo esto, pero si estoy fuera de la base, hágamelo saber. El concepto tenía sentido para mí. No estaba completamente convencido de que este fuera un escenario real que pudiera ocurrir en .NET. Nunca antes había escuchado hablar de "zombies", pero sí reconozco que los programadores que han trabajado en profundidad en niveles más bajos tienden a tener una comprensión más profunda de los fundamentos de computación (como el subprocesamiento). Sin embargo, definitivamente veo el valor del bloqueo, y he visto a muchos programadores de clase mundial aprovechar el bloqueo. También tengo una capacidad limitada para evaluar esto por mí mismo porque sé que la declaración de lock(obj)
es en realidad solo azúcar sintáctica para:
bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }
y porque Monitor.Enter
y Monitor.Exit
están marcados como extern
. Parece concebible que .NET realice algún tipo de procesamiento que proteja los subprocesos de la exposición a componentes del sistema que podrían tener este tipo de impacto, pero eso es puramente especulativo y probablemente solo se basa en el hecho de que nunca he oído hablar de "subprocesos zombi". antes de. Entonces, espero poder obtener algún comentario sobre esto aquí:
- ¿Hay una definición más clara de un "hilo zombie" de lo que he explicado aquí?
- ¿Pueden los hilos de zombis ocurrir en .NET? (¿Por qué por qué no?)
- Si corresponde, ¿cómo podría forzar la creación de un hilo zombie en .NET?
- Si corresponde, ¿cómo puedo aprovechar el bloqueo sin arriesgar un escenario de subproceso zombie en .NET?
Actualizar
Hice esta pregunta hace poco más de dos años. Hoy esto sucedió:
En este momento, la mayor parte de mi respuesta ha sido corregida por los comentarios a continuación. No eliminaré la respuesta porque necesito los puntos de reputación porque la información de los comentarios puede ser valiosa para los lectores.
Immortal Blue señaló que en .NET 2.0 y hasta los bloques finally
son inmunes a los abortos de hilos. Y como comenta Andreas Niedermair, esto puede no ser un hilo de zombie real, pero el siguiente ejemplo muestra cómo abortar un hilo puede causar problemas:
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");
}
}
Sin embargo, cuando se usa un lock() { }
, el finally
se ejecutará cuando se dispare una ThreadAbortException
esa manera.
La siguiente información, según resulta, solo es válida para .NET 1 y .NET 1.1:
Si dentro del lock() { }
produce otra excepción, y la excepción ThreadAbortException
llega exactamente cuando el finally
bloque está a punto de ejecutarse, el bloqueo no se libera. Como mencionaste, el lock() { }
se compila como:
finally
{
if (lockWasTaken)
Monitor.Exit(temp);
}
Si otro subproceso llama a Thread.Abort()
dentro del bloque finally
generado, es posible que no se libere el bloqueo.
En sistemas críticos con carga pesada, escribir código sin bloqueo es mejor principalmente debido a las mejoras de rendimiento. Mire cosas como LMAX y cómo aprovecha la "simpatía mecánica" para grandes discusiones sobre esto. Aunque te preocupes por los hilos de zombies? Creo que es un caso de borde que es solo un error que debe solucionarse, y no es razón suficiente para no usar el lock
.
¡Suena más como si tu amigo estuviera siendo elegante y ostentando su conocimiento de una terminología exótica oscura para mí! En todo el tiempo que estuve ejecutando los laboratorios de rendimiento en Microsoft UK, nunca encontré una instancia de este problema en .NET.
Esto no se trata de hilos de Zombie, pero el libro Effective C # tiene una sección sobre la implementación de IDisposable, (elemento 17), que trata sobre los objetos de Zombie que pensé que podrías encontrar interesantes.
Recomiendo leer el libro en sí, pero lo esencial es que si tienes una clase que implementa IDisposable o que contiene un Desctructor, lo único que debes hacer es liberar recursos. Si hace otras cosas aquí, entonces existe la posibilidad de que el objeto no se recoja, pero tampoco será accesible de ninguna manera.
Da un ejemplo similar al siguiente:
internal class Zombie
{
private static readonly List<Zombie> _undead = new List<Zombie>();
~Zombie()
{
_undead.Add(this);
}
}
Cuando se llama al destructor de este objeto, se coloca una referencia a sí mismo en la lista global, lo que significa que permanece vivo y en la memoria durante la vida del programa, pero no es accesible. Esto puede significar que los recursos (especialmente los recursos no administrados) pueden no estar totalmente liberados, lo que puede causar todo tipo de problemas potenciales.
Un ejemplo más completo está abajo. En el momento en que se alcanza el bucle foreach, tiene 150 objetos en la lista de no-muertos, cada uno con una imagen, pero la imagen ha sido GC y se obtiene una excepción si intenta usarla. En este ejemplo, obtengo una ArgumentException (el parámetro no es válido) cuando intento hacer algo con la imagen, ya sea que intente guardarla o incluso ver dimensiones como la altura y el ancho:
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);
}
}
Una vez más, soy consciente de que estabas preguntando acerca de los hilos de zombies en particular, pero el título de la pregunta es sobre zombies en .net, ¡y me acordé de esto y pensé que a otros les puede parecer interesante!
Puedo hacer hilos de zombis fácilmente.
var zombies = new List<Thread>();
while(true)
{
var th = new Thread(()=>{});
th.Start();
zombies.Add(th);
}
Esto filtra los manejadores de hilo (para Join()
). Es solo otra pérdida de memoria en lo que a nosotros respecta en el mundo administrado.
Ahora bien, matar un hilo de una manera que realmente mantiene cerraduras es un dolor en la parte trasera, pero es posible. El ExitThread()
otro tipo hace el trabajo. Como lo descubrió, el manejador de archivos fue limpiado por el gc pero un lock
alrededor de un objeto no lo hizo. ¿Pero por qué harías eso?
He limpiado un poco mi respuesta, pero he dejado la original a continuación como referencia
Es la primera vez que escucho el término zombies, así que asumo que su definición es:
Un hilo que ha terminado sin liberar todos sus recursos.
Entonces, dada esa definición, entonces sí, puedes hacerlo en .NET, como en otros lenguajes (C / C ++, java).
Sin embargo , no creo que esto sea una buena razón para no escribir código de misión crítica en .NET. Puede haber otras razones para decidir en contra de .NET, pero descartar .NET solo porque puedes tener hilos de zombis de alguna manera no tiene sentido para mí. Los subprocesos de zombis son posibles en C / C ++ (incluso diría que es mucho más fácil desordenar en C) y muchas de las aplicaciones de subprocesos críticas están en C / C ++ (comercio de gran volumen, bases de datos, etc.).
Conclusión Si está en el proceso de decidir el idioma que va a usar, le sugiero que tome en consideración el panorama general: rendimiento, habilidades de equipo, programación, integración con aplicaciones existentes, etc. Claro, los hilos de zombis son algo en lo que debería pensar Pero como es realmente difícil cometer este error en .NET en comparación con otros lenguajes como C, creo que esta preocupación se verá ensombrecida por otras cosas como las que se mencionaron anteriormente. ¡Buena suerte!
Los zombis de respuestas originales † pueden existir si no escribes el código de subprocesos adecuado. Lo mismo es cierto para otros lenguajes como C / C ++ y Java. Pero esta no es una razón para no escribir código de subprocesos en .NET.
Y al igual que con cualquier otro idioma, saber el precio antes de usar algo. También es útil saber qué sucede debajo del capó para que pueda prever cualquier problema potencial.
El código confiable para sistemas de misión crítica no es fácil de escribir, sea cual sea el idioma en el que se encuentre. Pero estoy seguro de que no es imposible hacerlo correctamente en .NET. También AFAIK, el subproceso de .NET no es tan diferente del subproceso en C / C ++, usa (o está construido a partir de) las mismas llamadas al sistema, excepto algunas construcciones específicas de .net (como las versiones de peso ligero de RWL y clases de eventos).
† la primera vez que escuché el término zombies, pero según su descripción, su colega probablemente significó un hilo que terminó sin liberar todos los recursos. Esto podría causar un bloqueo, una pérdida de memoria o algún otro efecto secundario negativo. Obviamente, esto no es deseable, pero seleccionar .NET debido a esta posibilidad probablemente no sea una buena idea, ya que también es posible en otros idiomas. Incluso diría que es más fácil desordenar en C / C ++ que en .NET (especialmente en C donde no tienes RAII), pero muchas aplicaciones críticas están escritas en C / C ++, ¿no? Así que realmente depende de sus circunstancias individuales. Si desea extraer cada onza de velocidad de su aplicación y desea estar lo más cerca posible del metal, entonces .NET podría no ser la mejor solución. Si tiene un presupuesto ajustado y hace mucha interacción con los servicios web / bibliotecas .net existentes / etc, entonces .NET puede ser una buena opción.
- ¿Hay una definición más clara de un "hilo zombie" de lo que he explicado aquí?
Me parece una buena explicación: un hilo que ha finalizado (y por lo tanto ya no puede liberar ningún recurso), pero cuyos recursos (por ejemplo, los manejadores) todavía están presentes y (potencialmente) causan problemas.
- ¿Pueden los hilos de zombis ocurrir en .NET? (¿Por qué por qué no?)
- Si corresponde, ¿cómo podría forzar la creación de un hilo zombie en .NET?
Ellos sí lo hacen, mira, yo hice uno!
[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);
}
}
Este programa inicia un Target
subproceso que abre un archivo y luego se mata inmediatamente utilizando ExitThread
. El subproceso zombie resultante nunca liberará el identificador del archivo "test.txt" y, por lo tanto, el archivo permanecerá abierto hasta que el programa finalice (puede verificarlo con el explorador de procesos o similar). El identificador de "test.txt" no se lanzará hasta que se GC.Collect
. Resulta que es aún más difícil de lo que pensé para crear un subproceso de zombi que pierde identificadores.
- Si corresponde, ¿cómo puedo aprovechar el bloqueo sin arriesgar un escenario de subproceso zombie en .NET?
¡No hagas lo que acabo de hacer!
Siempre y cuando su código se limpie correctamente (use Safe Handles o clases equivalentes si trabaja con recursos no administrados), y siempre que no haga todo lo posible para eliminar hilos de formas extrañas y maravillosas (la forma más segura es simplemente para no matar hilos: deje que se terminen normalmente o, si es necesario, a través de excepciones, la única forma de que tenga algo parecido a un hilo de zombie es si algo salió mal (por ejemplo, algo sale mal en el CLR).
De hecho, es realmente sorprendentemente difícil crear un subproceso zombie (tuve que P / Invocar en una función que es esencial que te diga en la documentación que no lo llames fuera de C). Por ejemplo, el siguiente código (horrible) en realidad no crea un hilo zombie.
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);
}
A pesar de cometer algunos errores terribles, el identificador de "test.txt" aún se cierra tan pronto como se llama Abort
(como parte del finalizador para el file
que debajo de las cubiertas usa SafeFileHandle para envolver su identificador de archivo)
El ejemplo de bloqueo en la respuesta de C.Evenhuis es probablemente la forma más fácil de fallar en liberar un recurso (un bloqueo en este caso) cuando un subproceso termina de una manera no extraña, pero eso se arregla fácilmente utilizando una instrucción de lock
. o poniendo el lanzamiento en un bloque finally
.
Ver también
- Las sutilezas de C # IL codegen para un caso muy sutil donde una excepción puede evitar que se libere un bloqueo incluso cuando se usa la palabra clave de
lock
(pero solo en .Net 3.5 y versiones anteriores) - Cerraduras y excepciones no se mezclan.