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:
- Esté atento a los caracteres entrantes del sistema remoto y muéstrelos
- Esté atento a las cosas que provienen del teclado y envíelas al sistema remoto
(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:
while get-character-from-remote:
print-to-screen character
El bucle para monitorear el teclado y enviar también es simple:
while get-character-from-keyboard:
send-to-remote character
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:
loop:
check-for-remote-character
if remote-character-is-ready:
print-to-screen character
check-for-keyboard-entry
if keyboard-is-ready:
send-to-remote character
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.