¿Qué son las corrutinas en c ++ 20?
¿En qué se diferencia de "Parallelism2" o "Concurrency2" (mire la imagen de abajo)?
La siguiente imagen es de ISOCPP.
¿Qué son las corrutinas en c ++ 20?
¿En qué se diferencia de "Parallelism2" o "Concurrency2" (mire la imagen de abajo)?
La siguiente imagen es de ISOCPP.
Respuestas:
En un nivel abstracto, Coroutines separó la idea de tener un estado de ejecución de la idea de tener un hilo de ejecución.
SIMD (instrucción única de múltiples datos) tiene múltiples "subprocesos de ejecución" pero solo un estado de ejecución (solo funciona con múltiples datos). Podría decirse que los algoritmos paralelos son un poco así, en el sentido de que tiene un "programa" que se ejecuta en diferentes datos.
El subproceso tiene varios "subprocesos de ejecución" y varios estados de ejecución. Tiene más de un programa y más de un hilo de ejecución.
Coroutines tiene varios estados de ejecución, pero no posee un hilo de ejecución. Tiene un programa y el programa tiene estado, pero no tiene hilo de ejecución.
El ejemplo más sencillo de corrutinas son los generadores o enumerables de otros lenguajes.
En pseudocódigo:
function Generator() {
for (i = 0 to 100)
produce i
}
Se Generator
llama y la primera vez que se llama vuelve 0
. Se recuerda su estado (cuánto varía el estado con la implementación de las corrutinas) y la próxima vez que lo llame continúa donde lo dejó. Entonces devuelve 1 la próxima vez. Entonces 2.
Finalmente llega al final del ciclo y cae al final de la función; la corrutina está terminada. (Lo que sucede aquí varía según el lenguaje del que estamos hablando; en Python, arroja una excepción).
Las corrutinas llevan esta capacidad a C ++.
Hay dos tipos de corrutinas; apilables y sin apilar.
Una corrutina sin pila solo almacena variables locales en su estado y ubicación de ejecución.
Una corrutina apilada almacena una pila completa (como un hilo).
Las corrutinas sin apilamiento pueden ser extremadamente ligeras. La última propuesta que leí involucraba básicamente reescribir su función en algo un poco como una lambda; todas las variables locales pasan al estado de un objeto, y las etiquetas se utilizan para saltar hacia / desde la ubicación donde la corrutina "produce" resultados intermedios.
El proceso de producir un valor se llama "rendimiento", ya que las corrutinas son un poco como multiproceso cooperativo; está cediendo el punto de ejecución a la persona que llama.
Boost tiene una implementación de corrutinas apiladas; le permite llamar a una función para ceder por usted. Las corrutinas apiladas son más potentes, pero también más caras.
Las corrutinas son más que un simple generador. Puede esperar una corrutina en una corrutina, lo que le permite componer corrutinas de una manera útil.
Las corrutinas, como if, bucles y llamadas a funciones, son otro tipo de "goto estructurado" que le permite expresar ciertos patrones útiles (como máquinas de estado) de una manera más natural.
La implementación específica de Coroutines en C ++ es un poco interesante.
En su nivel más básico, agrega algunas palabras clave a C ++:, co_return
co_await
co_yield
junto con algunos tipos de bibliotecas que funcionan con ellas.
Una función se convierte en una corrutina al tener una de esas en su cuerpo. Entonces, de su declaración, son indistinguibles de las funciones.
Cuando se utiliza una de esas tres palabras clave en el cuerpo de una función, se produce un examen obligatorio estándar del tipo de retorno y los argumentos y la función se transforma en una corrutina. Este examen le dice al compilador dónde almacenar el estado de la función cuando la función está suspendida.
La corrutina más simple es un generador:
generator<int> get_integers( int start=0, int step=1 ) {
for (int current=start; true; current+= step)
co_yield current;
}
co_yield
suspende la ejecución de funciones, almacena ese estado en generator<int>
, luego devuelve el valor de a current
través de generator<int>
.
Puede recorrer los enteros devueltos.
co_await
mientras tanto, le permite unir una corrutina con otra. Si está en una corrutina y necesita los resultados de algo que se espera (a menudo una corrutina) antes de progresar, hágalo co_await
. Si están listos, proceda de inmediato; si no, suspendes hasta que esté listo el awaitable que estás esperando.
std::future<std::expected<std::string>> load_data( std::string resource )
{
auto handle = co_await open_resouce(resource);
while( auto line = co_await read_line(handle)) {
if (std::optional<std::string> r = parse_data_from_line( line ))
co_return *r;
}
co_return std::unexpected( resource_lacks_data(resource) );
}
load_data
es una corrutina que genera un std::future
cuando se abre el recurso nombrado y logramos analizar hasta el punto donde encontramos los datos solicitados.
open_resource
y read_line
s son probablemente corrutinas asincrónicas que abren un archivo y leen líneas de él. El co_await
conecta el estado de suspensión y listo de load_data
con su progreso.
Las corrutinas de C ++ son mucho más flexibles que esto, ya que se implementaron como un conjunto mínimo de características de lenguaje además de los tipos de espacio de usuario. Los tipos de espacio de usuario definen efectivamente qué co_return
co_await
y co_yield
significan : he visto a personas usarlo para implementar expresiones opcionales monádicas, de modo que un co_await
en un opcional vacío automáticamente propaga el estado vacío al opcional externo:
modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
return (co_await a) + (co_await b);
}
en vez de
std::optional<int> add( std::optional<int> a, std::optional<int> b ) {
if (!a) return std::nullopt;
if (!b) return std::nullopt;
return *a + *b;
}
;;
.
Una corrutina es como una función C que tiene múltiples declaraciones de retorno y cuando se llama por segunda vez no inicia la ejecución al comienzo de la función sino en la primera instrucción después del retorno ejecutado anteriormente. Esta ubicación de ejecución se guarda junto con todas las variables automáticas que vivirían en la pila en funciones que no sean de rutina.
Una implementación de corrutina experimental anterior de Microsoft usó pilas copiadas para que incluso pudiera regresar de funciones anidadas en profundidad. Pero esta versión fue rechazada por el comité de C ++. Puede obtener esta implementación, por ejemplo, con la biblioteca de fibra Boosts.
Se supone que las corrutinas son (en C ++) funciones que pueden "esperar" a que se complete alguna otra rutina y proporcionar lo que sea necesario para que la rutina suspendida, en pausa, en espera continúe. la característica que es más interesante para la gente de C ++ es que las corrutinas idealmente no ocuparían espacio en la pila ... C # ya puede hacer algo como esto con await y yield, pero es posible que C ++ tenga que ser reconstruido para incorporarlo.
La concurrencia se centra en gran medida en la separación de preocupaciones cuando una preocupación es una tarea que se supone que el programa debe completar. esta separación de preocupaciones puede lograrse por varios medios ... por lo general, puede ser una delegación de algún tipo. la idea de concurrencia es que una serie de procesos podrían ejecutarse de forma independiente (separación de preocupaciones) y un 'oyente' dirigiría lo que sea producido por esas preocupaciones separadas a donde se supone que debe ir. esto depende en gran medida de algún tipo de gestión asincrónica. Hay varios enfoques para la concurrencia, incluida la programación orientada a aspectos y otros. C # tiene el operador 'delegado' que funciona bastante bien.
El paralelismo suena como concurrencia y puede estar involucrado, pero en realidad es una construcción física que involucra muchos procesadores dispuestos de una manera más o menos paralela con software que es capaz de dirigir partes de código a diferentes procesadores donde se ejecutará y los resultados se recibirán. sincrónicamente.