Pensé que el objetivo de una computadora de múltiples núcleos es que podría ejecutar múltiples hilos simultáneamente. En ese caso, si tiene una máquina de cuatro núcleos, ¿qué sentido tiene tener más de 4 subprocesos funcionando a la vez? ¿No estarían simplemente robando tiempo (recursos de CPU) el uno del otro?
multithreading
hardware
cpu-cores
Nick Heiner
fuente
fuente
Respuestas:
La respuesta gira en torno al propósito de los hilos, que es el paralelismo: ejecutar varias líneas de ejecución separadas a la vez. En un sistema 'ideal', tendría que ejecutar un subproceso por núcleo: sin interrupción. En realidad este no es el caso. Incluso si tiene cuatro núcleos y cuatro subprocesos de trabajo, su proceso y sus subprocesos se cambiarán constantemente por otros procesos y subprocesos. Si está ejecutando un sistema operativo moderno, cada proceso tiene al menos un hilo, y muchos tienen más. Todos estos procesos se ejecutan a la vez. Probablemente tenga varios cientos de hilos ejecutándose en su máquina en este momento. Nunca tendrás una situación en la que se ejecute un hilo sin que te roben el tiempo. (Bueno, podría hacerlo si se está ejecutando en tiempo real, si está utilizando un sistema operativo en tiempo real o, incluso en Windows, utilice una prioridad de subproceso en tiempo real. Pero es raro).
Con eso como fondo, la respuesta: Sí, más de cuatro subprocesos en una verdadera máquina de cuatro núcleos pueden darle una situación en la que 'se roban el tiempo el uno al otro', pero solo si cada subproceso individual necesita 100% de CPU . Si un subproceso no funciona al 100% (como un subproceso de interfaz de usuario podría no estarlo, o un subproceso que realiza una pequeña cantidad de trabajo o espera en otra cosa), otro subproceso programado es en realidad una buena situación.
En realidad es más complicado que eso:
¿Qué pasa si tiene cinco bits de trabajo que todos deben hacerse a la vez? Tiene más sentido ejecutarlos todos a la vez, que ejecutar cuatro de ellos y luego ejecutar el quinto más tarde.
Es raro que un hilo realmente necesite 100% de CPU. En el momento en que usa E / S de disco o red, por ejemplo, puede pasar tiempo esperando sin hacer nada útil. Esta es una situación muy común.
Si tiene un trabajo que debe ejecutarse, un mecanismo común es utilizar un conjunto de subprocesos. Puede tener sentido tener la misma cantidad de subprocesos que núcleos, pero el conjunto de subprocesos .Net tiene hasta 250 subprocesos disponibles por procesador . No estoy seguro de por qué hacen esto, pero supongo que tiene que ver con el tamaño de las tareas que se asignan para ejecutarse en los hilos.
Entonces: robar tiempo no es algo malo (y tampoco es realmente un robo: así es como se supone que funciona el sistema). Escriba sus programas multiproceso en función del tipo de trabajo que realizarán los hilos, que pueden no ser CPU -Unido. Calcule la cantidad de hilos que necesita según el perfil y la medición. Puede que le resulte más útil pensar en términos de tareas o trabajos, en lugar de hilos: escriba objetos de trabajo y déselos a un grupo para que se ejecuten. Finalmente, a menos que su programa sea realmente crítico para el rendimiento, no se preocupe demasiado :)
fuente
El hecho de que exista un hilo no siempre significa que se esté ejecutando activamente. Muchas aplicaciones de subprocesos involucran algunos de los subprocesos que se van a dormir hasta que es hora de que hagan algo, por ejemplo, la entrada del usuario que desencadena los subprocesos para despertarse, hacer un procesamiento y volver a dormir.
Esencialmente, los subprocesos son tareas individuales que pueden operar independientemente uno del otro, sin necesidad de estar al tanto del progreso de otra tarea. Es bastante posible tener más de estos de los que tienes la capacidad de correr simultáneamente; siguen siendo útiles por conveniencia, incluso si a veces tienen que esperar en fila uno detrás del otro.
fuente
El punto es que, a pesar de no obtener una aceleración real cuando el recuento de subprocesos excede el recuento de núcleos, puede usar subprocesos para desenredar piezas de lógica que no deberían ser interdependientes.
Incluso en una aplicación moderadamente compleja, el uso de un solo hilo tratar de hacer todo rápidamente hace que el código fluya rápidamente. El hilo único pasa la mayor parte de su tiempo sondeando esto, verificando eso, llamando condicionalmente a las rutinas según sea necesario, y se vuelve difícil ver algo más que un montón de minucias.
Compare esto con el caso en el que puede dedicar hilos a tareas para que, mirando cualquier hilo individual, pueda ver lo que está haciendo ese hilo. Por ejemplo, un hilo puede bloquear la espera de entrada desde un socket, analizar la secuencia en mensajes, filtrar mensajes y, cuando aparece un mensaje válido, pasarlo a otro hilo de trabajo. El subproceso de trabajo puede funcionar en entradas de varias otras fuentes. El código para cada uno de estos exhibirá un flujo limpio y decidido, sin tener que hacer comprobaciones explícitas de que no hay nada más que hacer.
Particionar el trabajo de esta manera permite que su aplicación confíe en el sistema operativo para programar qué hacer a continuación con la CPU, por lo que no tiene que hacer verificaciones condicionales explícitas en todas partes de su aplicación sobre lo que podría bloquearse y lo que está listo para procesar.
fuente
Si un subproceso está esperando un recurso (como cargar un valor de RAM en un registro, E / S de disco, acceso a la red, iniciar un nuevo proceso, consultar una base de datos o esperar la entrada del usuario), el procesador puede trabajar en un subproceso diferente y volver al primer subproceso una vez que el recurso esté disponible. Esto reduce el tiempo que la CPU pasa inactiva, ya que la CPU puede realizar millones de operaciones en lugar de permanecer inactiva.
Considere un hilo que necesita leer datos de un disco duro. En 2014, un núcleo de procesador típico opera a 2.5 GHz y puede ejecutar 4 instrucciones por ciclo. Con un tiempo de ciclo de 0.4 ns, el procesador puede ejecutar 10 instrucciones por nanosegundo. Con tiempos de búsqueda típicos del disco duro mecánico de alrededor de 10 milisegundos, el procesador es capaz de ejecutar 100 millones de instrucciones en el tiempo que lleva leer un valor del disco duro. Puede haber mejoras significativas en el rendimiento con discos duros con un pequeño caché (4 MB de búfer) y unidades híbridas con unos pocos GB de almacenamiento, ya que la latencia de datos para lecturas secuenciales o lecturas de la sección híbrida puede ser varios órdenes de magnitud más rápido.
Un núcleo de procesador puede cambiar entre subprocesos (el costo de pausar y reanudar un subproceso es de alrededor de 100 ciclos de reloj) mientras que el primer subproceso espera una entrada de alta latencia (algo más costoso que los registros (1 reloj) y RAM (5 nanosegundos)) Estos incluyen E / S de disco, acceso a la red (latencia de 250 ms), lectura de datos de un CD o un bus lento, o una llamada a la base de datos. Tener más hilos que núcleos significa que se puede hacer un trabajo útil mientras se resuelven las tareas de alta latencia.
La CPU tiene un programador de subprocesos que asigna prioridad a cada subproceso y permite que un subproceso se suspenda y luego se reanude después de un tiempo predeterminado. El trabajo del planificador de subprocesos es reducir la agitación, lo que ocurriría si cada subproceso ejecutara solo 100 instrucciones antes de volver a suspenderlo. La sobrecarga de subprocesos de conmutación reduciría el rendimiento útil total del núcleo del procesador.
Por esta razón, es posible que desee dividir su problema en un número razonable de subprocesos. Si estaba escribiendo código para realizar la multiplicación de matrices, crear un subproceso por celda en la matriz de salida podría ser excesivo, mientras que un subproceso por fila o por n filas en la matriz de salida podría reducir el costo general de crear, pausar y reanudar subprocesos.
Por eso también es importante la predicción de rama. Si tiene una instrucción if que requiere cargar un valor desde RAM pero el cuerpo de las instrucciones if y else utilizan valores ya cargados en los registros, el procesador puede ejecutar una o ambas ramas antes de que se haya evaluado la condición. Una vez que la condición regrese, el procesador aplicará el resultado de la rama correspondiente y descartará la otra. Realizar un trabajo potencialmente inútil aquí es probablemente mejor que cambiar a un subproceso diferente, lo que podría provocar una paliza.
A medida que nos alejamos de los procesadores de un solo núcleo de alta velocidad de reloj a los procesadores de múltiples núcleos, el diseño del chip se ha centrado en agrupar más núcleos por dado, mejorar el intercambio de recursos en el chip entre los núcleos, mejores algoritmos de predicción de ramificaciones, mejor sobrecarga de conmutación de hilos, y mejor programación de hilos.
fuente
La mayoría de las respuestas anteriores hablan sobre el rendimiento y la operación simultánea. Voy a abordar esto desde un ángulo diferente.
Tomemos el caso de, digamos, un programa de emulación de terminal simplista. Tienes que hacer lo siguiente:
(Los emuladores de terminales reales hacen más, incluso pueden hacer eco de las cosas que escribe en la pantalla también, pero lo pasaremos por ahora).
Ahora el ciclo para leer desde el control remoto es simple, según el siguiente pseudocódigo:
El bucle para monitorear el teclado y enviar también es simple:
Sin embargo, el problema es que tienes que hacer esto simultáneamente. El código ahora debe verse más así si no tiene subprocesos:
La lógica, incluso en este ejemplo deliberadamente simplificado que no tiene en cuenta la complejidad de las comunicaciones en el mundo real, es bastante confusa. Con el subprocesamiento, sin embargo, incluso en un solo núcleo, los dos bucles de pseudocódigo pueden existir independientemente sin entrelazar su lógica. Dado que ambos hilos estarán en su mayoría vinculados a E / S, no suponen una carga pesada para la CPU, a pesar de que, estrictamente hablando, desperdician más recursos de la CPU que el bucle integrado.
Ahora, por supuesto, el uso en el mundo real es más complicado que el anterior. Pero la complejidad del bucle integrado aumenta exponencialmente a medida que agrega más preocupaciones a la aplicación. La lógica se fragmenta cada vez más y debe comenzar a utilizar técnicas como máquinas de estado, corutinas, etc., para que las cosas sean manejables. Manejable, pero no legible. El enhebrado mantiene el código más legible.
Entonces, ¿por qué no usarías hilos?
Bueno, si sus tareas están vinculadas a la CPU en lugar de a las E / S, el subproceso en realidad ralentiza su sistema. El rendimiento sufrirá. Mucho, en muchos casos. ("Thrashing" es un problema común si suelta demasiados hilos enlazados a la CPU. Termina pasando más tiempo cambiando los hilos activos que ejecutando el contenido de los mismos hilos). Además, una de las razones por las que la lógica anterior es tan simple es que he elegido deliberadamente un ejemplo simplista (y poco realista). Si desea hacer eco de lo que se escribió en la pantalla, entonces tiene un nuevo mundo de dolor al introducir el bloqueo de los recursos compartidos. Con solo un recurso compartido, esto no es tanto un problema, pero comienza a convertirse en un problema cada vez mayor a medida que tiene más recursos para compartir.
Entonces, al final, el enhebrar es sobre muchas cosas. Por ejemplo, se trata de hacer que los procesos vinculados a E / S sean más receptivos (incluso si en general son menos eficientes) como algunos ya han dicho. También se trata de hacer que la lógica sea más fácil de seguir (pero solo si minimiza el estado compartido). Se trata de muchas cosas, y debe decidir si sus ventajas son mayores que sus desventajas caso por caso.
fuente
Aunque ciertamente puede usar hilos para acelerar los cálculos dependiendo de su hardware, uno de sus principales usos es hacer más de una cosa a la vez por razones de facilidad de uso.
Por ejemplo, si tiene que hacer un procesamiento en segundo plano y también responde a la entrada de la interfaz de usuario, puede usar hilos. Sin hilos, la interfaz de usuario se bloquea cada vez que intentas hacer un procesamiento pesado.
También vea esta pregunta relacionada: Usos prácticos para hilos
fuente
Estoy totalmente en desacuerdo con la afirmación de @ kyoryu de que el número ideal es un hilo por CPU.
Piénselo de esta manera: ¿por qué tenemos sistemas operativos de procesamiento múltiple? Durante la mayor parte del historial de la computadora, casi todas las computadoras tenían una CPU. Sin embargo, a partir de la década de 1960, todas las computadoras "reales" tenían sistemas operativos de procesamiento múltiple (también conocido como multitarea).
Ejecutas múltiples programas para que uno pueda ejecutarse mientras que otros están bloqueados por cosas como IO.
Dejemos a un lado los argumentos sobre si las versiones de Windows anteriores a NT eran multitarea. Desde entonces, cada sistema operativo real tenía múltiples tareas. Algunos no lo exponen a los usuarios, pero está ahí de todos modos, haciendo cosas como escuchar la radio del teléfono celular, hablar con el chip GPS, aceptar la entrada del mouse, etc.
Los hilos son solo tareas que son un poco más eficientes. No hay una diferencia fundamental entre una tarea, proceso e hilo.
Una CPU es un desperdicio terrible, así que tenga muchas cosas listas para usar cuando pueda.
Estoy de acuerdo en que con la mayoría de los lenguajes de procedimiento, C, C ++, Java, etc., escribir un código seguro para subprocesos adecuado es mucho trabajo. Con 6 CPU centrales en el mercado hoy en día y 16 CPU centrales no muy lejos, espero que la gente se aleje de estos idiomas antiguos, ya que el subprocesamiento múltiple es un requisito cada vez más crítico.
El desacuerdo con @kyoryu es solo en mi humilde opinión, el resto es un hecho.
fuente
Imagine un servidor web que tiene que atender un número arbitrario de solicitudes. Debe atender las solicitudes en paralelo porque, de lo contrario, cada nueva solicitud tiene que esperar hasta que se hayan completado todas las demás solicitudes (incluido el envío de la respuesta a través de Internet). En este caso, la mayoría de los servidores web tienen muchos menos núcleos que la cantidad de solicitudes que generalmente atienden.
También hace que sea más fácil para el desarrollador del servidor: solo tiene que escribir un programa de hilo que atienda una solicitud, no tiene que pensar en almacenar múltiples solicitudes, el orden en que las atiende, etc.
fuente
Muchos hilos estarán dormidos, esperando la entrada del usuario, E / S y otros eventos.
fuente
Los subprocesos pueden ayudar con la capacidad de respuesta en aplicaciones de IU. Además, puede usar hilos para obtener más trabajo de sus núcleos. Por ejemplo, en un solo núcleo, puede tener un hilo haciendo IO y otro haciendo algunos cálculos. Si fuera de un solo subproceso, el núcleo esencialmente podría estar inactivo esperando que se complete el IO. Ese es un ejemplo de alto nivel, pero los hilos definitivamente se pueden usar para golpear su CPU un poco más fuerte.
fuente
Un procesador, o CPU, es el chip físico que está conectado al sistema. Un procesador puede tener múltiples núcleos (un núcleo es la parte del chip que es capaz de ejecutar instrucciones). Un núcleo puede aparecer en el sistema operativo como múltiples procesadores virtuales si es capaz de ejecutar simultáneamente múltiples subprocesos (un subproceso es una sola secuencia de instrucciones).
Un proceso es otro nombre para una aplicación. En general, los procesos son independientes entre sí. Si un proceso muere, no causa que otro proceso también muera. Los procesos pueden comunicarse o compartir recursos como memoria o E / S.
Cada proceso tiene un espacio de direcciones y una pila separados. Un proceso puede contener múltiples hilos, cada uno capaz de ejecutar instrucciones simultáneamente. Todos los hilos en un proceso comparten el mismo espacio de direcciones, pero cada hilo tendrá su propia pila.
Afortunadamente, con estas definiciones y más investigación utilizando estos fundamentos ayudará a su comprensión.
fuente
El uso ideal de hilos es, de hecho, uno por núcleo.
Sin embargo, a menos que use exclusivamente IO asíncrono / sin bloqueo, existe una buena posibilidad de que tenga hilos bloqueados en IO en algún momento, lo que no usará su CPU.
Además, los lenguajes de programación típicos dificultan un poco el uso de 1 subproceso por CPU. Los lenguajes diseñados en torno a la concurrencia (como Erlang) pueden facilitar el uso de subprocesos adicionales.
fuente
La forma en que se diseñan algunas API, no tiene más remedio que ejecutarlas en un hilo separado (cualquier cosa con operaciones de bloqueo). Un ejemplo serían las bibliotecas HTTP de Python (AFAIK).
Por lo general, esto no es un gran problema (si es un problema, el sistema operativo o la API deben enviarse con un modo de operación asíncrono alternativo, es decir:)
select(2)
, porque probablemente significa que el subproceso estará inactivo durante la espera de I / O finalización. Por otro lado, si algo está haciendo un cálculo pesada, que tiene que ponerlo en un hilo separado que, por ejemplo, el hilo de interfaz gráfica de usuario (a menos que disfrutar de multiplexación manual).fuente
Sé que esta es una pregunta muy antigua con muchas buenas respuestas, pero estoy aquí para señalar algo que es importante en el entorno actual:
Si desea diseñar una aplicación para subprocesos múltiples, no debe diseñar para una configuración de hardware específica. La tecnología de la CPU ha avanzado bastante rápido durante años, y los recuentos de núcleos aumentan constantemente. Si diseña deliberadamente su aplicación de modo que use solo 4 hilos, entonces está potencialmente restringiéndose en un sistema octa-core (por ejemplo). Ahora, incluso los sistemas de 20 núcleos están disponibles comercialmente, por lo que tal diseño definitivamente está haciendo más daño que bien.
fuente
En respuesta a su primera conjetura: las máquinas multinúcleo pueden ejecutar simultáneamente múltiples procesos, no solo los múltiples hilos de un solo proceso.
En respuesta a su primera pregunta: el objetivo de varios subprocesos suele ser realizar simultáneamente múltiples tareas dentro de una aplicación. Los ejemplos clásicos en la red son un programa de correo electrónico que envía y recibe correo, y un servidor web que recibe y envía solicitudes de página. (Tenga en cuenta que es esencialmente imposible reducir un sistema como Windows para ejecutar solo un hilo o incluso un solo proceso. Ejecute el Administrador de tareas de Windows y normalmente verá una larga lista de procesos activos, muchos de los cuales ejecutarán múltiples hilos. )
En respuesta a su segunda pregunta: la mayoría de los procesos / subprocesos no están vinculados a la CPU (es decir, no se ejecutan de forma continua e ininterrumpida), sino que se detienen y esperan con frecuencia para que finalice la E / S. Durante esa espera, se pueden ejecutar otros procesos / subprocesos sin "robar" el código de espera (incluso en una máquina de núcleo único).
fuente
Un hilo es una abstracción que le permite escribir código tan simple como una secuencia de operación, felizmente inconsciente de que el código se ejecuta entrelazado con otro código, o estacionado esperando IO, o (tal vez algo más consciente de) esperando el otro hilo eventos o mensajes.
fuente
El punto es que la gran mayoría de los programadores no entienden cómo diseñar una máquina de estados. Ser capaz de poner todo en su propio hilo libera al programador de tener que pensar en cómo representar eficientemente el estado de los diferentes cálculos en progreso para que puedan ser interrumpidos y luego reanudados.
Como ejemplo, considere la compresión de video, una tarea que requiere mucha CPU. Si está utilizando una herramienta de interfaz gráfica de usuario, probablemente desee que la interfaz siga siendo receptiva (muestre el progreso, responda a las solicitudes de cancelación, redimensione las ventanas, etc.). Por lo tanto, diseña el software del codificador para procesar una unidad grande (uno o más cuadros) a la vez y ejecutarlo en su propio hilo, separado de la interfaz de usuario.
Por supuesto, una vez que se da cuenta de que hubiera sido bueno poder guardar el estado de codificación en progreso para poder cerrar el programa para reiniciar o jugar un juego que consume muchos recursos, se da cuenta de que debería haber aprendido cómo diseñar máquinas de estado desde el comenzando. O eso, o decides diseñar un problema completamente nuevo de proceso de hibernación de tu sistema operativo para que puedas suspender y reanudar aplicaciones individuales en el disco ...
fuente