Articulo de referencia

E/S asíncrona

La E/S asíncrona es una forma de procesamiento de entrada/salida que permite a un programa iniciar una operación de E/S y continuar procesando otras tareas antes de que finalice...

La E/S asíncrona es una forma de procesamiento de entrada/salida que permite a un programa iniciar una operación de E/S y continuar procesando otras tareas antes de que finalice la operación de E/S. A diferencia de la E/S no bloqueante, que devuelve el control inmediatamente pero puede requerir sondeos repetidos, la E/S asíncrona permite que el sistema o la API notifiquen al programa cuando finaliza la operación. El término "E/S superpuesta" se utiliza para la E/S asíncrona en la API de Windows .

Las operaciones de entrada y salida en una computadora pueden ser extremadamente lentas en comparación con el procesamiento de datos. Un dispositivo de E/S puede incorporar componentes mecánicos que deben moverse físicamente, como un disco duro que busca una pista para leer o escribir; esto suele ser mucho más lento que la conmutación de corriente eléctrica. Por ejemplo, durante una operación de disco que tarda diez milisegundos en completarse, un procesador con una frecuencia de reloj de un gigahercio podría haber realizado diez millones de ciclos de procesamiento de instrucciones.

Un enfoque sencillo para la entrada/salida (E/S) sería iniciar el acceso y esperar a que finalice. Sin embargo, este enfoque, denominado E/S síncrona o bloqueante , detendría el progreso del programa mientras se realiza la comunicación, dejando recursos del sistema inactivos. Cuando un programa realiza muchas operaciones de E/S (como un programa que depende principalmente de la entrada del usuario ), esto significa que el procesador puede pasar casi todo su tiempo inactivo esperando a que finalicen dichas operaciones.

Como alternativa, es posible iniciar la comunicación y luego realizar un procesamiento que no requiera que la operación de entrada/salida (E/S) se haya completado. Este enfoque se denomina entrada/salida asíncrona. Cualquier tarea que dependa de que la E/S se haya completado (esto incluye tanto el uso de los valores leídos como las operaciones críticas que pretenden asegurar que una operación de escritura se haya completado) aún debe esperar a que la operación de E/S finalice y, por lo tanto, permanece bloqueada, pero otros procesos que no dependen de la operación de E/S pueden continuar.

Existen numerosas funciones del sistema operativo para implementar E/S asíncrona en múltiples niveles. De hecho, una de las funciones principales de todos los sistemas operativos , salvo los más rudimentarios , es realizar al menos alguna forma de E/S asíncrona básica, aunque esto puede no ser particularmente evidente para el usuario o el programador. En la solución de software más simple, el estado del dispositivo de hardware se consulta a intervalos para detectar si está listo para su siguiente operación. (Por ejemplo, el sistema operativo CP/M se construyó de esta manera. Su semántica de llamadas al sistema no requería una estructura de E/S más elaborada, aunque la mayoría de las implementaciones eran más complejas y, por lo tanto, más eficientes). El acceso directo a memoria (DMA) puede aumentar considerablemente la eficiencia de un sistema basado en sondeo, y las interrupciones de hardware pueden eliminar por completo la necesidad de sondeo. Los sistemas operativos multitarea pueden aprovechar la funcionalidad que proporcionan las interrupciones de hardware, ocultando al usuario la complejidad de su gestión. El spooling fue una de las primeras formas de multitarea diseñadas para aprovechar la E/S asíncrona. Por último, el uso de multihilo y las API de E/S asíncronas explícitas dentro de los procesos de usuario pueden aprovechar aún más la E/S asíncrona, a costa de una mayor complejidad del software.

La E/S asíncrona se utiliza para mejorar la eficiencia energética y, en algunos casos, el rendimiento. Sin embargo, en ciertos casos puede tener efectos negativos en la latencia y el rendimiento.

Formularios

Formas de entrada/salida y ejemplos de funciones POSIX:

Todas las formas de E/S asíncrona exponen las aplicaciones a posibles conflictos de recursos y fallos asociados. Se requiere una programación cuidadosa (a menudo utilizando exclusión mutua , semáforos , etc.) para evitarlo.

Al exponer E/S asíncrona a las aplicaciones, existen varias clases generales de implementación. La forma de la API proporcionada a la aplicación no necesariamente se corresponde con el mecanismo que ofrece el sistema operativo; es posible realizar emulaciones. Además, una misma aplicación puede utilizar más de un método, según sus necesidades y las preferencias de sus programadores. Muchos sistemas operativos ofrecen más de uno de estos mecanismos, e incluso algunos pueden ofrecerlos todos.

Proceso

Disponible en versiones tempranas de Unix. En un sistema operativo multitarea , el procesamiento se puede distribuir entre diferentes procesos, que se ejecutan de forma independiente, tienen su propia memoria y procesan sus propios flujos de E/S; estos flujos suelen estar conectados en tuberías . Los procesos son bastante costosos de crear y mantener, [ 1 ] por lo que esta solución solo funciona bien si el conjunto de procesos es pequeño y relativamente estable. También presupone que los procesos individuales pueden operar de forma independiente, salvo para procesar las E/S de los demás; si necesitan comunicarse de otras maneras, coordinarlos puede resultar difícil.

Una extensión de este enfoque es la programación de flujo de datos , que permite crear redes más complejas que las simples cadenas que admiten las tuberías.

Votación

Variaciones:

  • Error si aún no se puede realizar (volver a publicar más tarde).
  • Informe cuándo se puede hacer sin bloquear (y luego publíquelo).

El sondeo proporciona una API síncrona sin bloqueo que puede utilizarse para implementar alguna API asíncrona. Está disponible en sistemas Unix y Windows tradicionales . Su principal problema es que puede consumir tiempo de CPU realizando sondeos repetidamente cuando el proceso emisor no tiene nada más que hacer, lo que reduce el tiempo disponible para otros procesos. Además, dado que una aplicación de sondeo es esencialmente de un solo hilo, puede que no pueda aprovechar al máximo el paralelismo de E/S del que es capaz el hardware.

Bucles de selección (o sondeo)

Disponible en BSD Unix y casi cualquier otro sistema con una pila de protocolos TCP/IP que utilice o se base en la implementación de BSD. Una variación del tema del sondeo, un bucle select utiliza la llamada al sistema para esperar hasta que se cumpla una condición en un descriptor de archivo (por ejemplo, cuando hay datos disponibles para leer), se produzca un tiempo de espera o se reciba una señal (por ejemplo, cuando un proceso hijo termina). Al examinar los parámetros de retorno de la llamada, el bucle averigua qué descriptor de archivo ha cambiado y ejecuta el código apropiado. A menudo, para facilitar su uso, el bucle select se implementa como un bucle de eventos , quizás utilizando funciones de devolución de llamada ; esta situación se presta particularmente bien a la programación orientada a eventos .selectselect

Si bien este método es fiable y relativamente eficiente, depende en gran medida del paradigma Unix de que " todo es un archivo "; cualquier E/S bloqueante que no involucre un descriptor de archivo bloqueará el proceso. El bucle select también depende de poder involucrar toda la E/S en la selectllamada central; las bibliotecas que realizan su propia E/S son particularmente problemáticas en este sentido. Un problema potencial adicional es que las operaciones select y de E/S aún están suficientemente desacopladas como para que el resultado de select pueda ser engañoso: si dos procesos están leyendo de un solo descriptor de archivo (un diseño posiblemente deficiente), select puede indicar la disponibilidad de datos leídos que han desaparecido para cuando se emite la lectura, lo que resulta en un bloqueo; si dos procesos están escribiendo en un solo descriptor de archivo (algo bastante común), select puede indicar escritura inmediata, pero la escritura aún puede bloquearse, porque el otro proceso ha llenado un búfer mientras tanto, o porque la escritura es demasiado grande para el búfer disponible o por otras razones inadecuadas para el destinatario.

El bucle select no alcanza la máxima eficiencia del sistema posible con, por ejemplo, el método de colas de finalizaciónselect , porque la semántica de la llamada, que permite el ajuste por llamada del conjunto de eventos aceptables, consume cierta cantidad de tiempo por invocación al recorrer la matriz de selección. Esto crea poca sobrecarga para las aplicaciones de usuario que podrían tener abierto un descriptor de archivo para el sistema de ventanas y algunos para archivos abiertos, pero se convierte en un problema mayor a medida que crece el número de posibles fuentes de eventos, y puede obstaculizar el desarrollo de aplicaciones de servidor de muchos clientes, como en el problema C10k ; otros métodos asíncronos pueden ser notablemente más eficientes en tales casos. Algunos Unix proporcionan llamadas específicas del sistema con mejor escalabilidad; por ejemplo, epollen Linux (que llena la matriz de selección de retorno solo con aquellas fuentes de eventos en las que ha ocurrido un evento), kqueueen FreeBSD , y puertos de eventos (y /dev/poll) en Solaris .

SVR3 Unix proporcionaba la pollllamada al sistema. Podría decirse que tiene un nombre mejor que select, pero para los fines de esta discusión es esencialmente lo mismo. Los sistemas SVR4 Unix (y por lo tanto POSIX ) ofrecen ambas llamadas.

Señales (interrupciones)

Disponible en sistemas BSD y POSIX Unix. La E/S se ejecuta de forma asíncrona y, al completarse, se genera una señal ( interrupción ). Al igual que en la programación de bajo nivel del kernel, las funcionalidades disponibles para un uso seguro dentro del manejador de señales son limitadas, y el flujo principal del proceso podría interrumpirse en casi cualquier punto, lo que resultaría en estructuras de datos inconsistentes para el manejador de señales. Por lo general, el manejador de señales no puede generar más operaciones de E/S asíncronas por sí mismo.

El enfoque de señales , si bien es relativamente sencillo de implementar en el sistema operativo, conlleva para el programa de aplicación la desventaja de escribir el sistema de interrupciones del núcleo del sistema operativo. Su peor característica es que cada llamada al sistema síncrona (bloqueante) es potencialmente interrumpible; por lo general, el programador debe incorporar código de reintento en cada llamada.

Funciones de devolución de llamada

Disponible en Mac OS clásico , VMS y Windows . Presenta muchas características del método de señales , ya que fundamentalmente es lo mismo, aunque rara vez se reconoce como tal. La diferencia radica en que cada solicitud de E/S suele tener su propia función de finalización, mientras que el sistema de señales tiene una única función de devolución de llamada.

Por otro lado, un problema potencial del uso de devoluciones de llamada es que la profundidad de la pila puede crecer de forma inmanejable, ya que es muy común programar otra operación de E/S al finalizar una. Si esta debe satisfacerse inmediatamente, la primera devolución de llamada no se libera de la pila antes de que se invoque la siguiente. Los sistemas para evitar esto (como la programación intermedia de nuevas tareas) añaden complejidad y reducen el rendimiento. Sin embargo, en la práctica, esto generalmente no representa un problema, ya que la nueva operación de E/S suele finalizar tan pronto como se inicia, lo que permite liberar la pila. El problema también puede evitarse impidiendo nuevas devoluciones de llamada mediante una cola, hasta que la primera devolución de llamada finalice.

Procesos o hilos ligeros

Los procesos ligeros (LWP) o hilos están disponibles en la mayoría de los sistemas operativos modernos. Son similares al método de procesos , pero con menor sobrecarga y sin el aislamiento de datos que dificulta la coordinación de los flujos. Cada LWP o hilo utiliza E/S síncrona de bloqueo tradicional, lo que simplifica la lógica de programación; este es un paradigma común en muchos lenguajes de programación, incluidos Java y Rust. La programación multihilo requiere el uso de mecanismos de sincronización proporcionados por el kernel y bibliotecas seguras para hilos . Este método es el más adecuado para aplicaciones de gran escala, como los servidores web, debido a la gran cantidad de hilos necesarios.

Este enfoque también se utiliza en el sistema de ejecución del lenguaje de programación Erlang . La máquina virtual de Erlang utiliza E/S asíncrona mediante un pequeño grupo de solo unos pocos hilos o, a veces, un solo proceso, para gestionar la E/S de hasta millones de procesos Erlang. La gestión de E/S en cada proceso se implementa principalmente mediante E/S síncrona bloqueante. De esta forma, el alto rendimiento de la E/S asíncrona se combina con la simplicidad de la E/S convencional (véase el modelo Actor ). Muchos problemas de E/S en Erlang se asignan al paso de mensajes , que se puede procesar fácilmente mediante la recepción selectiva integrada.

Las fibras / coroutines pueden considerarse un enfoque igualmente ligero para realizar E/S asíncronas fuera del sistema de ejecución de Erlang, aunque no ofrecen exactamente las mismas garantías que los procesos de Erlang.

Colas/puertos de finalización

Disponible en Microsoft Windows , Solaris , AmigaOS , DNIX y Linux (usando io_uring , disponible en 5.1 y superior). [ 2 ] Las solicitudes de E/S se emiten de forma asíncrona, pero las notificaciones de finalización se proporcionan a través de un mecanismo de cola de sincronización en el orden en que se completan. Generalmente asociado con una estructuración de máquina de estados del proceso principal ( programación orientada a eventos ), que puede tener poca semejanza con un proceso que no usa E/S asíncrona o que usa una de las otras formas, lo que dificulta la reutilización del código . No requiere mecanismos de sincronización especiales adicionales ni bibliotecas seguras para subprocesos , ni están separados los flujos textuales (código) y de tiempo (evento).

Banderas de eventos

Disponible en VMS y AmigaOS (a menudo se usa junto con un puerto de finalización). Presenta muchas de las características del método de cola de finalización , ya que es esencialmente una cola de finalización de profundidad uno. Para simular el efecto de la "profundidad" de la cola, se requiere un indicador de evento adicional para cada evento potencialmente no procesado (pero completado), de lo contrario, se puede perder información del evento. Esperar al siguiente evento disponible en dicho grupo requiere mecanismos de sincronización que pueden no ser escalables a un mayor número de eventos potencialmente paralelos.

Entrada/salida de canal

Disponible en mainframes de IBM , Groupe Bull y Unisys . La E/S de canal está diseñada para maximizar la utilización de la CPU y el rendimiento al descargar la mayor parte de las operaciones de E/S a un coprocesador. El coprocesador cuenta con DMA integrado, gestiona las interrupciones de los dispositivos, es controlado por la CPU principal y solo interrumpe a esta última cuando es estrictamente necesario. Esta arquitectura también admite los denominados programas de canal, que se ejecutan en el procesador de canal para realizar las tareas más complejas relacionadas con las actividades y los protocolos de E/S.

E/S registradas

Disponible en Windows Server 2012 y Windows 8. Optimizado para aplicaciones que procesan grandes cantidades de mensajes pequeños para lograr un mayor número de operaciones de E/S por segundo con menor fluctuación y latencia. [ 3 ]

Implementación

La gran mayoría del hardware informático de propósito general se basa completamente en dos métodos para implementar E/S asíncronas: sondeo e interrupciones. Generalmente, ambos métodos se utilizan conjuntamente, y el equilibrio depende en gran medida del diseño del hardware y de sus características de rendimiento requeridas. ( El DMA no es un método independiente en sí mismo, sino simplemente un medio para realizar más trabajo por sondeo o interrupción).

Los sistemas de sondeo puro son totalmente posibles; los microcontroladores pequeños (como los que utilizan el microcontrolador PIC ) suelen diseñarse de esta manera. Los sistemas CP/M también podrían diseñarse así (aunque rara vez se hacía), con o sin DMA. Además, cuando se requiere el máximo rendimiento para solo unas pocas tareas, a costa de otras posibles, el sondeo puede ser apropiado, ya que la sobrecarga que supone gestionar interrupciones puede resultar indeseable. (Atender una interrupción requiere tiempo [y espacio] para guardar al menos parte del estado del procesador, además del tiempo necesario para reanudar la tarea interrumpida).

La mayoría de los sistemas informáticos de propósito general dependen en gran medida de las interrupciones. Si bien es posible implementar un sistema de interrupciones puro, generalmente se requiere algún componente de sondeo, ya que es muy común que múltiples fuentes potenciales de interrupciones compartan una misma línea de señal. En este caso, el controlador del dispositivo utiliza el sondeo para determinar la fuente real. (Este tiempo de resolución también contribuye a la penalización en el rendimiento del sistema de interrupciones. A lo largo de los años, se ha trabajado intensamente para minimizar la sobrecarga asociada al procesamiento de interrupciones. Los sistemas de interrupciones actuales son bastante lentos en comparación con algunos sistemas anteriores altamente optimizados, pero el aumento general del rendimiento del hardware ha mitigado considerablemente este problema).

También son posibles los enfoques híbridos, en los que una interrupción puede desencadenar el inicio de una ráfaga de E/S asíncrona, y se utiliza el sondeo dentro de la propia ráfaga. Esta técnica es común en controladores de dispositivos de alta velocidad, como los de red o disco, donde el tiempo perdido al regresar a la tarea previa a la interrupción es mayor que el tiempo hasta el siguiente servicio requerido. (El hardware de E/S común que se usa actualmente depende en gran medida del DMA y de grandes búferes de datos para compensar un sistema de interrupciones de rendimiento relativamente bajo. Estos sistemas suelen utilizar el sondeo dentro de los bucles del controlador y pueden ofrecer un rendimiento extraordinario. Idealmente, los sondeos por dato siempre son exitosos o, como máximo, se repiten un número reducido de veces).

En el pasado, este tipo de enfoque híbrido era común en los controladores de disco y red donde no se disponía de DMA ni de un almacenamiento en búfer significativo. Dado que las velocidades de transferencia deseadas eran incluso más rápidas de lo que podía soportar el bucle mínimo de cuatro operaciones por dato (prueba de bit, salto condicional a sí mismo, búsqueda y almacenamiento), el hardware solía diseñarse con generación automática de estado de espera en el dispositivo de E/S, trasladando la comprobación de disponibilidad de datos del software al hardware de búsqueda o almacenamiento del procesador y reduciendo el bucle programado a dos operaciones. (En efecto, utilizando el propio procesador como motor DMA). El procesador 6502 ofrecía un método inusual para proporcionar un bucle de tres elementos por dato, ya que disponía de un pin de hardware que, al activarse, hacía que el bit de desbordamiento del procesador se estableciera directamente. (Obviamente, había que tener mucho cuidado en el diseño del hardware para evitar sobrescribir el bit de desbordamiento fuera del controlador del dispositivo).

Síntesis

Utilizando únicamente estas dos herramientas (sondeo e interrupciones), todas las demás formas de E/S asíncrona mencionadas anteriormente pueden sintetizarse (y de hecho, se sintetizan).

En un entorno como una máquina virtual Java (JVM), la E/S asíncrona puede sintetizarse incluso si el entorno en el que se ejecuta la JVM no la ofrece. Esto se debe a la naturaleza interpretada de la JVM. La JVM puede realizar sondeos (o recibir una interrupción) periódicamente para instituir un cambio en el flujo de control interno, lo que da la apariencia de múltiples procesos simultáneos, algunos de los cuales presumiblemente existen para realizar E/S asíncrona. (Por supuesto, a nivel microscópico el paralelismo puede ser bastante burdo y presentar algunas características no ideales, pero superficialmente parecerá ser el deseado).

Ese es, de hecho, el problema de usar el sondeo, en cualquiera de sus formas, para sintetizar una forma diferente de E/S asíncrona. Cada ciclo de CPU que se dedica al sondeo se desperdicia y se pierde en sobrecarga en lugar de realizar la tarea deseada. Cada ciclo de CPU que no se dedica al sondeo representa un aumento en la latencia de respuesta a las E/S pendientes. Lograr un equilibrio aceptable entre estas dos fuerzas opuestas es difícil. (Por eso se inventaron los sistemas de interrupción por hardware).

La clave para maximizar la eficiencia reside en minimizar el trabajo necesario para activar la aplicación correspondiente al recibir una interrupción. En segundo lugar (aunque quizás no menos importante) se encuentra el método que la propia aplicación utiliza para determinar qué debe hacer.

Los métodos de sondeo expuestos, incluidos los mecanismos de selección/sondeo, resultan particularmente problemáticos (para la eficiencia de las aplicaciones). Si bien es muy probable que los eventos de E/S subyacentes de interés se generen mediante interrupciones, la interacción con este mecanismo se realiza mediante sondeo, lo que puede consumir mucho tiempo. Esto es especialmente cierto en el caso del sondeo a gran escala que se puede realizar mediante selección (y sondeo). Las interrupciones se corresponden muy bien con señales, funciones de devolución de llamada, colas de finalización e indicadores de eventos, por lo que estos sistemas pueden ser muy eficientes.

Ejemplos

Los siguientes ejemplos muestran tres enfoques para leer datos de entrada/salida. Los objetos y las funciones son abstractos.

1. Bloqueante, síncrono:

dispositivo : Dispositivo = IO.open ( ) datos : Datos = dispositivo.read ( ) # El hilo se bloqueará hasta que haya datos en el dispositivo print ( datos )

2. Bloqueante y no bloqueante, síncrono: (aquí IO.poll()se bloquea hasta por 5 segundos, pero device.read()no lo hace)

dispositivo : Dispositivo = IO.open () listo : bool = False mientras no esté listo : print ( " ¡ No hay datos para leer!" ) listo = IO.poll ( dispositivo , IO.INPUT , 5 ) # devuelve el control si han transcurrido 5 segundos o hay datos para leer (INPUT ) datos : Datos = dispositivo.read ( ) print ( datos )

3. Sin bloqueo, asíncrono:

ios : IOService = IO.IOService ( ) dispositivo : Dispositivo = IO.open ( ios )def input_handler ( data : Data , err : Error ) -> None : """Manejador de datos de entrada""" if not err : print ( data )dispositivo.read_some ( input_handler ) ios.loop ( ) # esperar hasta que todas las operaciones se hayan completado y llamar a todos los manejadores apropiados

Aquí está el mismo ejemplo con async/await :

ios : IOService = IO.IOService ( ) dispositivo : Dispositivo = IO.open ( ios )async def tarea () -> Ninguno : intentar : datos : Datos = await dispositivo . leer_algo () imprimir ( datos ) excepto Excepción : pasarios.addTask ( task ) ios.loop ( ) # esperar hasta que todas las operaciones se hayan completado y llamar a todos los manejadores apropiados

Aquí tienes un ejemplo con el patrón Reactor :

dispositivo : Dispositivo = IO.open ( ) reactor : Reactor = IO.Reactor ( )def input_handler ( data : Data ) -> None : """Manejador de datos de entrada""" print ( data ) reactor . stop ()reactor.add_handler ( input_handler , device , IO.INPUT ) reactor.run ( ) # ejecuta el reactor, que maneja los eventos y llama a los manejadores apropiados

Véase también

Referencias

  1. "Hilos de usuario de primera clase" . dl.acm.org . doi : 10.1145/121133.344329 . Consultado el 13 de abril de 2026 .
  2. Corbet, Jonathan. "Lanzamiento de una nueva API de E/S asíncrona" . LWN.net . Consultado el 27 de julio de 2020 .
  3. "Extensiones de la API de entrada/salida registrada (RIO)" . technet.microsoft.com . 31 de agosto de 2016.
  • El problema C10K : un estudio de los métodos de E/S asíncronos con énfasis en la escalabilidad – por Dan Kegel
  • Artículo " Mejora el rendimiento de las aplicaciones mediante E/S asíncrona " de M. Tim Jones
  • Artículo " E/S asíncrona perezosa para servidores controlados por eventos " por Willy Zwaenepoel , Khaled Elmeleegy , Anupam Chanda y Alan L. Cox
  • Realizar operaciones de E/S en paralelo
  • Descripción del estándar POSIX
  • Puertos de finalización de E/S por Mark Russinovich
  • Descripción de la Guía del desarrollador de .NET Framework
  • E/S asíncrona y el Explorador de E/S de disco asíncrona
  • IO::AIO es un módulo de Perl que ofrece una interfaz asíncrona para la mayoría de las operaciones de entrada/salida.
  • ACE Proactor