Estaba teniendo una discusión con un compañero de equipo sobre bloquear en .NET. Es un tipo realmente brillante con una amplia experiencia tanto en programación de nivel inferior como de nivel superior, pero su experiencia con la programación de nivel inferior supera con creces la mía. De todos modos, argumentó que el bloqueo de .NET debe evitarse en los sistemas críticos que se espera que estén sometidos a una gran carga, si es posible, para evitar la pequeña posibilidad de que un "hilo zombie" estrelle un sistema. Usualmente uso el bloqueo y no sabía qué era un "hilo zombie", 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 conserva algunos recursos. Un ejemplo que dio de cómo un hilo zombie podría romper un sistema fue que un hilo comienza un procedimiento después de bloquear un objeto, y luego termina en algún momento antes de que se pueda liberar el bloqueo. Esta situación tiene el potencial de bloquear el sistema, porque eventualmente, los intentos de ejecutar ese método darán como resultado que todos los hilos esperen el acceso a un objeto que nunca será devuelto, porque el hilo que está usando el objeto bloqueado está muerto.
Creo que entendí la esencia de esto, pero si estoy fuera de la base, por favor hágamelo saber. El concepto tenía sentido para mí. No estaba completamente convencido de que este fuera un escenario real que podría suceder en .NET. Nunca antes había oído hablar de "zombis", pero sí reconozco que los programadores que han trabajado en profundidad en los niveles inferiores tienden a tener una comprensión más profunda de los fundamentos de la informática (como el subprocesamiento). Sin embargo, definitivamente veo el valor en el 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 lock(obj)
declaración es realmente 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 extern
. Parece concebible que .NET realice algún tipo de procesamiento que proteja los hilos de la exposición a los componentes del sistema que podrían tener este tipo de impacto, pero eso es puramente especulativo y probablemente solo se base en el hecho de que nunca he oído hablar de "hilos de zombis" antes de. Entonces, espero poder obtener algunos comentarios sobre esto aquí:
- ¿Existe una definición más clara de "hilo zombie" que la que he explicado aquí?
- ¿Pueden aparecer hilos zombie 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 sucedió esto:
fuente
wait
owaitpid
. El proceso hijo se denomina "proceso zombie". Ver también howtogeek.com/119815Respuestas:
Parece una buena explicación para mí: un hilo que ha terminado (y por lo tanto ya no puede liberar ningún recurso), pero cuyos recursos (por ejemplo, identificadores) todavía están alrededor y (potencialmente) causan problemas.
Seguro que sí, mira, ¡hice uno!
Este programa inicia un hilo
Target
que abre un archivo y luego se mata inmediatamente usandoExitThread
.El hilo zombie resultante nunca liberará el identificador del archivo "test.txt" y, por lo tanto, el archivo permanecerá abierto hasta que finalice el programa (puede consultar con el explorador de procesos o similar).El identificador de "test.txt" no se lanzará hasta queGC.Collect
se llame; resulta que es aún más difícil de lo que pensé para crear un hilo zombie que filtra los identificadores)¡No hagas lo que acabo de hacer!
Siempre que su código se limpie correctamente después de sí mismo (use Safe Handles o clases equivalentes si trabaja con recursos no administrados), y siempre y cuando no salga de su camino para matar hilos de maneras extrañas y maravillosas (la forma más segura es solo para nunca matar subprocesos: deje que terminen normalmente, o mediante excepciones si es necesario), la única forma en que tendrá algo parecido a un hilo zombie es si algo ha salido muy mal (por ejemplo, algo sale mal en el CLR).
De hecho, es realmente sorprendente crear un hilo zombie (tuve que P / Invocar en una función que esencialmente te dice 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.
A pesar de cometer algunos errores bastante horribles, el identificador de "test.txt" todavía se cierra tan pronto como
Abort
se lo llama (como parte del finalizador para elfile
cual debajo de las cubiertas usa SafeFileHandle para ajustar su identificador de archivo)El ejemplo de bloqueo en la respuesta de C.Evenhuis es probablemente la forma más fácil de no liberar un recurso (un bloqueo en este caso) cuando un subproceso se termina de una manera no extraña, pero eso se soluciona fácilmente mediante el uso de una
lock
declaración o poniendo el lanzamiento en unfinally
bloque.Ver también
lock
palabra clave (pero solo en .Net 3.5 y versiones anteriores)fuente
ExitThread
embargo, soy un poco escéptico de tu llamada. Obviamente, funciona, pero se siente más como un truco inteligente que como un escenario realista. Uno de mis objetivos es aprender qué no hacer para no crear accidentalmente hilos zombies con código .NET. Probablemente podría haber descubierto que llamar al código C ++ que se sabe que causa ese problema desde el código .NET produciría el efecto deseado. Obviamente sabes mucho sobre estas cosas. ¿Conoces otros casos (posiblemente extraños, pero no lo suficientemente extraños como para nunca suceder involuntariamente) con el mismo resultado?Set excelInstance = GetObject(, "Excel.Application")
Limpié un poco mi respuesta, pero dejé la original a continuación como referencia.
Es la primera vez que escucho sobre el término zombis, así que asumiré que su definición es:
Un hilo que ha terminado sin liberar todos sus recursos
Entonces, dada esa definición, entonces sí, puede hacerlo en .NET, como con otros lenguajes (C / C ++, java).
Sin embargo , no creo que esta sea una buena razón para no escribir código de misión crítica con hilos en .NET. Puede haber otras razones para decidir en contra de .NET, pero descartar .NET solo porque puede tener hilos zombie de alguna manera no tiene sentido para mí. Los hilos de Zombie son posibles en C / C ++ (incluso diría que es mucho más fácil desordenar en C) y muchas aplicaciones críticas y enhebradas están en C / C ++ (comercio de alto volumen, bases de datos, etc.).
Conclusión Si está en el proceso de decidir qué idioma utilizar, le sugiero que tenga en cuenta el panorama general: rendimiento, habilidades de equipo, programación, integración con aplicaciones existentes, etc. Seguro, los hilos zombies son algo en lo que debe pensar , pero dado que es muy difícil cometer este error en .NET en comparación con otros lenguajes como C, creo que esta preocupación se verá eclipsada por otras cosas como las mencionadas anteriormente. ¡Buena suerte!
Original Response Zombies † puede existir si no escribe el código de subproceso 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 roscado en .NET.
Y al igual que con cualquier otro idioma, conozca el precio antes de usar algo. También ayuda saber qué sucede debajo del capó para poder prever cualquier problema potencial.
El código confiable para sistemas de misión crítica no es fácil de escribir, independientemente del idioma en el que se encuentre. Pero estoy seguro de que no es imposible hacerlo correctamente en .NET. También AFAIK, el enhebrado .NET no es tan diferente del enhebrado en C / C ++, usa (o está construido a partir de) las mismas llamadas del sistema, excepto algunas construcciones específicas de .net (como las versiones livianas de RWL y clases de eventos).
† La primera vez que escuché hablar del término zombis, pero según tu descripción, tu colega probablemente significó un hilo que terminó sin liberar todos los recursos. Esto podría causar un punto muerto, pérdida de memoria o algún otro efecto secundario negativo. Obviamente, esto no es deseable, pero señalar .NET debido a esta posibilidad probablemente no sea una buena idea, ya que también es posible en otros lenguajes. Incluso diría que es más fácil equivocarse en C / C ++ que en .NET (especialmente en C donde no tiene RAII), pero muchas aplicaciones críticas están escritas en C / C ++, ¿verdad? Por lo tanto, realmente depende de sus circunstancias individuales. Si desea extraer cada onza de velocidad de su aplicación y desea acercarse lo más posible al metal desnudo, entonces .NET podríaNo sea la mejor solución. Si tiene un presupuesto ajustado e interactúa mucho con los servicios web / bibliotecas .net existentes / etc., entonces .NET puede ser una buena opción.
fuente
extern
métodos sin salida . Si tienes una sugerencia, me encantaría escucharla. (2) Estoy de acuerdo en que es posible hacerlo en .NET. Me gustaría creer que es posible con el bloqueo, pero aún no he encontrado una respuesta satisfactoria para justificar eso hoylocking
es lalock
palabra clave, entonces probablemente no, ya que serializa la ejecución. Para maximizar el rendimiento, tendría que usar las construcciones correctas dependiendo de las características de su código. Diría que si puede escribir código confiable en c / c ++ / java / lo que sea usando pthreads / boost / thread pools / lo que sea, entonces también puede escribirlo en C #. Pero si no puede escribir código confiable en cualquier idioma usando cualquier biblioteca, entonces dudo que escribir en C # sea diferente.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ónporque la información en los comentarios puede ser valiosa para los lectores.Immortal Blue señaló que en .NET 2.0 y los
finally
bloques superiores son inmunes a los abortos de subprocesos. Y como comentó Andreas Niedermair, este puede no ser un hilo zombie real, pero el siguiente ejemplo muestra cómo abortar un hilo puede causar problemas:Sin embargo, cuando se usa un
lock() { }
bloque,finally
aún se ejecutará cuando aThreadAbortException
se dispara de esa manera.La siguiente información, como resultado, solo es válida para .NET 1 y .NET 1.1:
Si dentro del
lock() { }
bloque ocurre otra excepción, yThreadAbortException
llega exactamente cuando elfinally
bloque está a punto de ejecutarse, el bloqueo no se libera. Como mencionó, ellock() { }
bloque se compila como:Si otro hilo llama
Thread.Abort()
dentro delfinally
bloque generado , es posible que no se libere el bloqueo.fuente
lock()
pero no puedo ver ningún uso de esto ... entonces, ¿cómo está conectado esto? este es un uso "incorrecto" deMonitor.Enter
yMonitor.Exit
(sin el uso detry
yfinally
)Monitor.Enter
yMonitor.Exit
sin el uso adecuado detry
yfinally
, de todos modos, su escenario bloqueará otros hilos que podrían aferrarse_lock
, por lo que hay un escenario de bloqueo, no necesariamente un hilo zombie ... Además, no está liberando el bloqueoMain
... pero, oye ... tal vez el OP se está bloqueando por bloqueo en lugar de hilos de zombies :)lock
o adecuadatry
/finally
-usageNo se trata de hilos de Zombie, pero el libro Effective C # tiene una sección sobre la implementación de IDisposable, (ítem 17), que habla sobre los objetos de Zombie que pensé que podrían 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 recolecte basura, pero tampoco será accesible de ninguna manera.
Da un ejemplo similar al siguiente:
Cuando se llama al destructor en este objeto, se coloca una referencia a sí mismo en la lista global, lo que significa que permanece vivo y en memoria durante la vida del programa, pero no es accesible. Esto puede significar que los recursos (particularmente los recursos no administrados) pueden no liberarse por completo, lo que puede causar todo tipo de problemas potenciales.
Un ejemplo más completo está debajo. Para cuando 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'd y obtiene una excepción si intenta usarla. En este ejemplo, obtengo una excepción ArgumentException (el parámetro no es válido) cuando intento hacer algo con la imagen, ya sea que trate de guardarla o incluso ver dimensiones como la altura y el ancho:
Una vez más, soy consciente de que estabas preguntando sobre 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 otros podrían encontrarlo interesante!
fuente
_undead
destinado a ser estático?NullReferenceException
, porque tengo la sensación de que lo que falta debe estar más relacionado con la máquina que con la aplicación. ¿Es esto correcto?IDisposable
ese es el problema. Me preocupan los finalizadores (destructores), pero el solo hecho deIDisposable
no hacer que un objeto vaya a la cola del finalizador y arriesgue este escenario zombie. Esta advertencia se refiere a los finalizadores, y podrían llamarse métodos de eliminación. Hay ejemplos dondeIDisposable
se usa en tipos sin finalizadores. El sentimiento debería ser para la limpieza de recursos, pero eso podría ser una limpieza de recursos no trivial. RX utiliza válidamenteIDisposable
para la limpieza de suscripciones y puede llamar a otros recursos posteriores. (Yo tampoco voté en contra ...)En sistemas críticos bajo una gran carga, 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. Sin embargo, ¿te preocupan los hilos de zombis? Creo que es un caso límite que es solo un error que se debe solucionar, y no es una razón suficiente para no usarlo
lock
.¡Parece que tu amigo solo es elegante y hace alarde de su conocimiento de una oscura y exótica terminología 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.
fuente
lock
declaración!lock
bloques grandes y relativamente fáciles de comprender son relativamente buenos. Evitaría introducir la complejidad en aras de la optimización del rendimiento hasta que sepa que lo necesita.Estoy de acuerdo en que existen "Zombie Threads", es un término para referirse a lo que sucede con los Threads que se quedan con recursos que no dejan ir y, sin embargo, no mueren por completo, de ahí el nombre de "zombie", por lo que su ¡La explicación de esta referencia es bastante acertada!
Sí, pueden ocurrir. Es una referencia, y en realidad Windows se refiere como "zombie": MSDN usa la palabra "Zombie" para procesos / hilos muertos
Suceder con frecuencia es otra historia, y depende de tus técnicas y prácticas de codificación, en cuanto a ti, que te gusta Thread Locking y lo has hecho por un tiempo, ni siquiera me preocuparía de que te ocurriera ese escenario.
Y sí, como @KevinPanko mencionó correctamente en los comentarios, "Zombie Threads" provienen de Unix, razón por la cual se usan en XCode-ObjectiveC y se conocen como "NSZombie" y se usan para la depuración. Se comporta más o menos de la misma manera ... la única diferencia es que un objeto que debería haber muerto se convierte en un "Objeto Zombie" para la depuración en lugar del "Hilo Zombie" que podría ser un problema potencial en su código.
fuente
Puedo hacer hilos de zombies con bastante facilidad.
Esto pierde las asas del 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 mantenga bloqueos es un dolor en la parte trasera, pero es posible. El otro tipo
ExitThread()
hace el trabajo. Como descubrió, el gc limpió el identificador del archivo, pero nolock
alrededor de un objeto. ¿Pero por qué harías eso?fuente