multithreading - thread - ¿Por qué los hilos pueden ser considerados "malvados"?
thread safe java (11)
Estaba leyendo las preguntas frecuentes de SQLite y encontré este pasaje:
Los hilos son malos. Evítales.
No entiendo muy bien la afirmación "Los hilos son malos". Si eso es verdad, entonces ¿cuál es la alternativa?
Mi comprensión superficial de los hilos es:
- Los hilos hacen que la concurrencia ocurra. De lo contrario, se perderá la potencia de la CPU, esperando (p. Ej.) Una E / S lenta.
- Pero lo malo es que debe sincronizar su lógica para evitar la contención y tiene que proteger los recursos compartidos.
Nota: Como no estoy familiarizado con los subprocesos en Windows, espero que la discusión se limite a los subprocesos de Linux / Unix.
Crear una gran cantidad de subprocesos sin restricciones es realmente malo ... el uso de un mecanismo de agrupación (agrupación de subprocesos) mitigará este problema.
Otra forma en que los subprocesos son "malos" es que la mayoría de los códigos de marco no están diseñados para tratar con varios subprocesos, por lo que debe administrar su propio mecanismo de bloqueo para esas estructuras de datos.
Los hilos son buenos, pero debe pensar en cómo y cuándo los usa y recordar medir si realmente hay un beneficio en el rendimiento.
Cuando la gente dice que "los hilos son malos", generalmente lo hacen en el contexto de decir "los procesos son buenos". Los subprocesos comparten implícitamente todo el estado de la aplicación y los manejadores (y los locales de subprocesos son opcionales). Esto significa que hay muchas oportunidades para olvidarse de sincronizar (¡o incluso no entender que necesita sincronizar!) Al acceder a los datos compartidos.
Los procesos tienen espacio de memoria separado, y cualquier comunicación entre ellos es explícita. Además, las primitivas utilizadas para la comunicación entre procesos son a menudo tales que no es necesario que se sincronice (por ejemplo, tuberías). Y aún puede compartir el estado directamente si lo necesita, usando la memoria compartida, pero eso también es explícito en cada instancia dada. Así que hay menos oportunidades para cometer errores, y la intención del código es más explícita.
El papel al que te vinculas parece explicarse muy bien. ¿Lo leíste?
Tenga en cuenta que un subproceso puede referirse al constructo de lenguaje de programación (como en la mayoría de los lenguajes de procedimiento o OOP, usted crea un subproceso manualmente y le dice que ejecute una función), o pueden referirse al constructo de hardware (cada núcleo de CPU ejecuta un hilo a la vez).
El subproceso a nivel de hardware es obviamente inevitable, es simplemente cómo funciona la CPU. Pero a la CPU no le importa cómo se expresa la concurrencia en su código fuente. No tiene que ser por una llamada a la función "comenzar cadena", por ejemplo. El sistema operativo y la CPU solo tienen que saber qué subprocesos de instrucciones deben ejecutarse.
Su punto es que si utilizáramos mejores lenguajes que C o Java con un modelo de programación diseñado para la concurrencia, podríamos obtener la concurrencia básicamente de forma gratuita. Si hubiésemos utilizado un lenguaje de paso de mensajes o uno funcional sin efectos secundarios, el compilador podría paralizar nuestro código para nosotros. Y funcionaría.
En un sentido simple, puede pensar en un hilo como otro puntero de instrucción en el proceso actual. En otras palabras, apunta la IP de otro procesador a algún código en el mismo ejecutable. Entonces, en lugar de tener un puntero de instrucción moviéndose a través del código, hay dos o más instrucciones de ejecución de IP desde el mismo ejecutable y el mismo espacio de direcciones simultáneamente.
Recuerde que el ejecutable tiene su propio espacio de direcciones con datos / pila, etc. Entonces, ahora que se ejecutan dos o más instrucciones simultáneamente, puede imaginar lo que sucede cuando más de una de las instrucciones desea leer / escribir en la misma dirección de memoria en al mismo tiempo.
El problema es que los subprocesos están operando dentro del espacio de direcciones del proceso y no cuentan con mecanismos de protección del procesador como lo son los procesos completos. (Bifurcar un proceso en UNIX es una práctica estándar y simplemente crea otro proceso).
Los subprocesos fuera de control pueden consumir ciclos de CPU, masticar RAM, provocar ejecuciones, etc., etc., y la única forma de detenerlos es decirle al programador de procesos del SO que termine forzadamente el subproceso anulando su indicador de instrucción (es decir, que deje de ejecutar) . Si le dice a la CPU a la fuerza que deje de ejecutar una secuencia de instrucciones, ¿qué sucede con los recursos que se han asignado o están siendo operados por esas instrucciones? ¿Se quedan en estado estable? ¿Están bien liberados? etc ...
Entonces, sí, los hilos requieren más pensamiento y responsabilidad que ejecutar un proceso debido a los recursos compartidos.
Lo que hace que los hilos sean "malos" es que una vez que introduce más de una secuencia de ejecución en su programa, ya no puede contar con que su programa se comporte de una manera determinista.
Es decir, dado el mismo conjunto de entradas, un programa de un solo hilo (en la mayoría de los casos) siempre hará lo mismo.
Un programa de subprocesos múltiples, dado el mismo conjunto de entradas, puede hacer algo diferente cada vez que se ejecuta, a menos que se controle con mucho cuidado. Esto se debe a que el programador de subprocesos del sistema operativo, combinado con un temporizador del sistema, determina el orden en que los distintos subprocesos ejecutan diferentes bits de código, y esto introduce una gran cantidad de "aleatoriedad" en lo que hace el programa cuando se ejecuta.
El resultado es que la depuración de un programa de subprocesos múltiples puede ser mucho más difícil que la depuración de un programa de un solo subproceso, porque si no sabes lo que estás haciendo puede ser muy fácil terminar con una condición de carrera o un error de interbloqueo que solo Aparece (aparentemente) al azar una o dos veces al mes. El programa se verá bien para su departamento de control de calidad (ya que no tienen un mes para ejecutarlo) pero una vez que esté en el campo, escuchará a los clientes que el programa se bloqueó y nadie puede reproducir el bloqueo. .. bleah.
En resumen, los hilos no son realmente "malos", pero son juju fuertes y no deben usarse a menos que (a) realmente los necesites y (b) sepas en qué te estás metiendo. Si los usas, úsalos lo más moderadamente posible, e intenta que su comportamiento sea lo más estúpido posible que puedas. Especialmente con el multihilo, si algo puede salir mal, tarde o temprano lo hará.
Los hilos no son más "malos" que los martillos o destornilladores o cualquier otra herramienta; sólo requieren habilidad para utilizar. La solución no es evitarlos; Es para educarte y mejorar tu conjunto de habilidades.
Para cualquier aplicación que requiera una ejecución estable y segura durante largos períodos de tiempo sin fallas ni mantenimiento, los subprocesos siempre son un error tentador. Invariablemente resultan ser más problemáticos de lo que valen. Producen resultados rápidos y prototipos que parecen estar funcionando correctamente, pero después de un par de semanas o meses, descubres que tienen fallas críticas.
Como lo mencionó otro póster, una vez que usa un solo hilo en su programa, ahora ha abierto un camino no determinista de ejecución de código que puede producir un número casi infinito de conflictos en el tiempo, el uso compartido de memoria y las condiciones de carrera. La mayoría de las expresiones de confianza para resolver estos problemas son expresadas por personas que han aprendido los principios de la programación de múltiples hilos pero que aún tienen que experimentar las dificultades para resolverlos.
Los hilos son malos. Los buenos programadores los evitan donde sea humanamente posible. La alternativa de forking se ofreció aquí y, a menudo, es una buena estrategia para muchas aplicaciones. La noción de dividir su código en procesos de ejecución separados que se ejecutan con algún tipo de acoplamiento suelto a menudo resulta ser una excelente estrategia en plataformas que lo admiten. Los hilos que se ejecutan juntos en un solo programa no son una solución. Por lo general, es la creación de un defecto arquitectónico fatal en su diseño que solo puede remediarse realmente reescribiendo todo el programa.
La reciente tendencia hacia la concurrencia orientada a eventos es una excelente innovación de desarrollo. Este tipo de programas usualmente demuestran tener una gran resistencia después de ser implementados.
Nunca he conocido a un joven ingeniero que no pensara que los hilos fueran geniales. Nunca he conocido a un ingeniero de más edad que no los rechazara como a la plaga.
Respuesta simple como yo lo entiendo ...
La mayoría de los modelos de subprocesos utilizan "concurrencia de estado compartido", lo que significa que dos procesos de ejecución pueden compartir la misma memoria al mismo tiempo. Si un hilo no sabe lo que hace el otro, puede modificar los datos de una manera que el otro hilo no espera. Esto causa errores.
Los subprocesos son "malos" porque necesitas envolver tu mente alrededor de todos los subprocesos que trabajan en la misma memoria al mismo tiempo, y todas las cosas divertidas que la acompañan (puntos muertos, condiciones de carrera, etc.).
Puede leer sobre los modelos de concurrencia de Clojure (estructuras de datos inmutables) y Erlang (paso de mensajes) para obtener ideas alternativas sobre cómo lograr fines similares.
Siendo un ingeniero de mayor edad, estoy muy de acuerdo con la respuesta de Texas Arcane.
Los hilos son MUY malvados porque causan errores que son extremadamente difíciles de resolver. Literalmente he pasado meses resolviendo condiciones de carrera esporádicas. Un ejemplo hizo que los tranvías se detuvieran repentinamente una vez al mes en el medio de la carretera y bloquearan el tráfico hasta que fueran remolcados. Por suerte no creé el error, pero sí pasé 4 meses a tiempo completo para resolverlo ...
Es un poco tarde para agregar a este hilo, pero me gustaría mencionar una alternativa muy interesante a los hilos: programación asíncrona con co-rutinas y ciclos de eventos. Esto está siendo soportado por más y más idiomas, y no tiene el problema de las condiciones de carrera como lo ha hecho el multihilo.
Puede reemplazar los subprocesos múltiples en los casos en que se usa para esperar eventos de múltiples fuentes, pero no donde los cálculos deben realizarse en paralelo en múltiples núcleos de CPU.
Un hilo es un poco como un proceso de peso ligero. Piense en ello como una ruta de ejecución independiente dentro de una aplicación. El hilo se ejecuta en el mismo espacio de memoria que la aplicación y, por lo tanto, tiene acceso a todos los mismos recursos, objetos globales y variables globales.
Lo bueno de ellos: puede paralelizar un programa para mejorar el rendimiento. Algunos ejemplos, 1) En un programa de edición de imágenes, un subproceso puede ejecutar el procesamiento del filtro independientemente de la GUI. 2) Algunos algoritmos se prestan a múltiples hilos.
¿Qué hay de malo en ellos? Si un programa está mal diseñado, pueden provocar problemas de interbloqueo en los que ambos subprocesos se esperan entre sí para acceder al mismo recurso. Y en segundo lugar, el diseño del programa puede ser más complejo debido a esto. Además, algunas bibliotecas de clases no admiten subprocesos. por ejemplo, la función de biblioteca c "strtok" no es "segura para subprocesos". En otras palabras, si dos subprocesos lo usaran al mismo tiempo, obstruirían los resultados de cada uno. Afortunadamente, a menudo hay alternativas seguras para subprocesos ... por ejemplo, aumentar la biblioteca
Los hilos no son malos, pueden ser muy útiles.
En Linux / Unix, los subprocesos no han sido bien soportados en el pasado, aunque creo que Linux ahora tiene soporte para subprocesos Posix y otros unices son compatibles con subprocesos ahora a través de bibliotecas o de forma nativa. es decir, pthreads.
La alternativa más común a los subprocesos en plataformas Linux / Unix es la bifurcación. Fork es simplemente una copia de un programa que incluye los manejadores de archivos abiertos y las variables globales. fork () devuelve 0 al proceso hijo y la identificación del proceso al padre. Es una forma más antigua de hacer las cosas en Linux / Unix, pero todavía se usa bien. Los hilos utilizan menos memoria que los fork y son más rápidos de iniciar. Además, las comunicaciones entre procesos son más trabajo que simples hilos.
Yo lo interpretaría de otra manera. No es que los hilos sean malos, sino que los efectos secundarios son malos en un contexto multiproceso (que es mucho menos llamativo).
Un efecto secundario en este contexto es algo que afecta el estado compartido por más de un hilo, ya sea global o simplemente compartido. Hace poco escribí una reseña de Spring Batch y uno de los fragmentos de código utilizados es:
private static Map<Long, JobExecution> executionsById = TransactionAwareProxyFactory.createTransactionalMap();
private static long currentId = 0;
public void saveJobExecution(JobExecution jobExecution) {
Assert.isTrue(jobExecution.getId() == null);
Long newId = currentId++;
jobExecution.setId(newId);
jobExecution.incrementVersion();
executionsById.put(newId, copy(jobExecution));
}
Ahora hay al menos tres problemas graves de subprocesos en menos de 10 líneas de código aquí. Un ejemplo de un efecto secundario en este contexto sería actualizar la variable estática currentId.
La programación funcional (Haskell, Scheme, Ocaml, Lisp, otros) tiende a proponer funciones "puras". Una función pura es aquella sin efectos secundarios. Muchos lenguajes imperativos (por ejemplo, Java, C #) también fomentan el uso de objetos inmutables (un objeto inmutable es aquel cuyo estado no puede cambiar una vez creado).
La razón de (o al menos el efecto de) estas dos cosas es en gran medida la misma: hacen que el código multiproceso sea mucho más fácil. Una función pura por definición es threadsafe. Un objeto inmutable por definición es threadsafe.
La ventaja de los procesos es que hay menos estado compartido (generalmente). En la programación tradicional UNIX C, hacer un fork () para crear un nuevo proceso resultaría en un estado de proceso compartido y esto se usó como un medio de IPC (comunicación entre procesos) pero generalmente ese estado se reemplaza (con exec ()) con algo más.
Pero los subprocesos son mucho más baratos de crear y destruir y requieren menos recursos del sistema (de hecho, la operación en sí misma puede no tener un concepto de subprocesos, pero aún puede crear programas de multiproceso). Estos se llaman hilos verdes .