¿Diferencia entre una "corrutina" y un "hilo"?
¿Cuáles son las diferencias entre una "corrutina" y un "hilo"?
Primera lectura: Concurrencia vs Paralelismo: ¿Cuál es la diferencia?
La concurrencia es la separación de tareas para proporcionar una ejecución entrelazada. El paralelismo es la ejecución simultánea de múltiples trabajos para aumentar la velocidad. — https://github.com/servo/servo/wiki/Design
Respuesta corta: con los subprocesos, el sistema operativo cambia los subprocesos en ejecución de forma preventiva según su programador, que es un algoritmo en el núcleo del sistema operativo. Con las corrutinas, el programador y el lenguaje de programación determinan cuándo cambiar de rutina; en otras palabras, las tareas se realizan de forma cooperativa a la vez al pausar y reanudar funciones en puntos establecidos, normalmente (pero no necesariamente) dentro de un solo hilo.
Respuesta larga: a diferencia de los subprocesos, que el sistema operativo programa de forma preventiva, los cambios de rutina son cooperativos, lo que significa que el programador (y posiblemente el lenguaje de programación y su tiempo de ejecución) controla cuándo se producirá un cambio.
A diferencia de los subprocesos, que son preventivos, los cambios de rutina son cooperativos (el programador controla cuándo ocurrirá un cambio). El kernel no participa en los cambios de rutina. — http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/coroutine/overview.html
Un lenguaje que admite subprocesos nativos puede ejecutar sus subprocesos (subprocesos de usuario) en los subprocesos del sistema operativo ( subprocesos del núcleo ). Cada proceso tiene al menos un subproceso del núcleo. Los subprocesos del kernel son como procesos, excepto que comparten espacio de memoria en su propio proceso con todos los demás subprocesos de ese proceso. Un proceso "es dueño" de todos sus recursos asignados, como memoria, identificadores de archivos, sockets, identificadores de dispositivos, etc., y todos estos recursos se comparten entre los subprocesos del núcleo.
El programador del sistema operativo es parte del kernel que ejecuta cada subproceso durante un tiempo determinado (en una máquina con un solo procesador). El programador asigna tiempo (segmentación de tiempo) a cada subproceso, y si el subproceso no finaliza dentro de ese tiempo, el programador se adelanta (lo interrumpe y cambia a otro subproceso). Se pueden ejecutar varios subprocesos en paralelo en una máquina multiprocesador, ya que cada subproceso puede programarse (pero no necesariamente debe programarse) en un procesador independiente.
En una máquina con un solo procesador, los subprocesos se dividen en intervalos de tiempo y se adelantan (cambian entre ellos) rápidamente (en Linux, el intervalo de tiempo predeterminado es 100 ms), lo que los hace concurrentes. Sin embargo, no se pueden ejecutar en paralelo (simultáneamente), ya que un procesador de un solo núcleo sólo puede ejecutar una cosa a la vez.
Se pueden utilizar corrutinas y/o generadores para implementar funciones cooperativas. En lugar de ejecutarse en subprocesos del núcleo y ser programados por el sistema operativo, se ejecutan en un solo subproceso hasta que ceden o finalizan, cediendo a otras funciones según lo determine el programador. Se pueden utilizar lenguajes con generadores , como Python y ECMAScript 6, para crear corrutinas. Async/await (visto en C#, Python, ECMAscript 7, Rust) es una abstracción construida sobre funciones generadoras que generan futuros/promesas.
En algunos contextos, las corrutinas pueden referirse a funciones apilables, mientras que los generadores pueden referirse a funciones sin pila.
Fibras , hilos livianos e hilos verdes son otros nombres para corrutinas o cosas similares a corrutinas. A veces pueden parecerse (normalmente a propósito) más a subprocesos del sistema operativo en el lenguaje de programación, pero no se ejecutan en paralelo como subprocesos reales y, en cambio, funcionan como corrutinas. (Puede haber particularidades técnicas más específicas o diferencias entre estos conceptos según el lenguaje o la implementación).
Por ejemplo, Java tenía " hilos verdes "; estos eran subprocesos programados por la máquina virtual Java (JVM) en lugar de hacerlo de forma nativa en los subprocesos del kernel del sistema operativo subyacente. Estos no se ejecutaron en paralelo ni aprovecharon múltiples procesadores/núcleos, ¡ya que eso requeriría un subproceso nativo! Como no estaban programados por el sistema operativo, se parecían más a corrutinas que a subprocesos del kernel. Los subprocesos verdes son los que utilizaba Java hasta que se introdujeron los subprocesos nativos en Java 1.2.
Los hilos consumen recursos. En la JVM, cada subproceso tiene su propia pila, normalmente de 1 MB de tamaño. 64k es la cantidad mínima de espacio de pila permitida por subproceso en la JVM. El tamaño de la pila de subprocesos se puede configurar en la línea de comando de la JVM. A pesar del nombre, los subprocesos no son gratuitos, debido a su uso de recursos, como que cada subproceso necesita su propia pila, almacenamiento local de subprocesos (si lo hay) y el costo de programación de subprocesos/cambio de contexto/invalidación de caché de CPU. Esta es parte de la razón por la cual las corrutinas se han vuelto populares para aplicaciones altamente concurrentes y críticas para el rendimiento.
Mac OS solo permitirá que un proceso asigne alrededor de 2000 subprocesos, y Linux asigna una pila de 8 MB por subproceso y solo permitirá tantos subprocesos como quepan en la RAM física.
Por lo tanto, los subprocesos son los más pesados (en términos de uso de memoria y tiempo de cambio de contexto), luego las corrutinas y finalmente los generadores son los más livianos.
Aproximadamente 7 años de retraso, pero a las respuestas aquí les falta algo de contexto sobre las co-rutinas frente a los subprocesos. ¿Por qué las corrutinas reciben tanta atención últimamente y cuándo las usaría en comparación con los subprocesos ?
En primer lugar, si las corrutinas se ejecutan simultáneamente (nunca en paralelo ), ¿por qué alguien las preferiría a los subprocesos?
La respuesta es que las corrutinas pueden proporcionar un nivel muy alto de concurrencia con muy pocos gastos generales . Generalmente, en un entorno de subprocesos, tiene como máximo entre 30 y 50 subprocesos antes de que la cantidad de gastos generales desperdiciados en la programación de estos subprocesos (por parte del programador del sistema) reduzca significativamente la cantidad de tiempo que los subprocesos realmente realizan un trabajo útil.
Ok, con los subprocesos puedes tener paralelismo, pero no demasiado, ¿no es eso mejor que una co-rutina que se ejecuta en un solo subproceso? Bueno, no necesariamente. Recuerde que una co-rutina aún puede realizar concurrencia sin sobrecarga del programador: simplemente administra el cambio de contexto.
Por ejemplo, si tiene una rutina realizando algún trabajo y realiza una operación que sabe que se bloqueará durante algún tiempo (es decir, una solicitud de red), con una co-rutina puede cambiar inmediatamente a otra rutina sin la sobrecarga de incluir el programador del sistema en esta decisión: sí, usted, el programador, debe especificar cuándo pueden cambiar las rutinas conjuntas.
Con muchas rutinas que realizan tareas muy pequeñas y cambian voluntariamente entre sí, ha alcanzado un nivel de eficiencia que ningún programador podría alcanzar. Ahora puedes tener miles de corrutinas trabajando juntas en lugar de decenas de subprocesos.
Debido a que sus rutinas ahora cambian entre sí en puntos predeterminados, ahora también puede evitar bloquear estructuras de datos compartidas (porque nunca le diría a su código que cambie a otra rutina en medio de una sección crítica).
Otro beneficio es el uso de memoria mucho menor. Con el modelo de subprocesos, cada subproceso necesita asignar su propia pila, por lo que su uso de memoria crece linealmente con la cantidad de subprocesos que tiene. Con las co-rutinas, la cantidad de rutinas que tienes no tiene una relación directa con el uso de tu memoria.
Y, finalmente, las co-rutinas están recibiendo mucha atención porque en algunos lenguajes de programación (como Python) sus subprocesos no pueden ejecutarse en paralelo de todos modos : se ejecutan simultáneamente al igual que las corrutinas, pero sin poca memoria y sin sobrecarga de programación.
Las corrutinas son una forma de procesamiento secuencial: solo se ejecuta una en un momento dado (al igual que las subrutinas, también conocidas como procedimientos, también conocidas como funciones, simplemente se pasan el testigo entre sí de manera más fluida).
Los subprocesos son (al menos conceptualmente) una forma de procesamiento concurrente: se pueden ejecutar varios subprocesos en un momento dado. (Tradicionalmente, en máquinas de un solo núcleo y CPU, esa concurrencia se simulaba con algo de ayuda del sistema operativo; hoy en día, dado que tantas máquinas tienen múltiples CPU y/o múltiples núcleos, los subprocesos de facto se ejecutarán simultáneamente. no sólo "conceptualmente").