Cuando hablan de la ejecución del programa, entonces "ejecución asincrónica" significa una situación en la que el programa no espera la finalización de un determinado proceso, sino que continúa trabajando independientemente de él. Un ejemplo de programación asincrónica es una utilidad que, trabajando de forma asincrónica, escribe en un archivo de registro. Aunque dicha utilidad puede fallar (por ejemplo, debido a la falta de espacio libre en el disco), en la mayoría de los casos funcionará correctamente y se puede usar en varios programas. Podrán llamarla, pasarle los datos para que la graben, y luego podrán continuar haciendo lo suyo.

El uso de mecanismos asincrónicos al escribir un determinado programa significa que este programa se ejecutará más rápido que sin usar tales mecanismos. Al mismo tiempo, lo que se planea lanzar de forma asincrónica, como una utilidad para iniciar sesión, debe escribirse teniendo en cuenta las emergencias. Por ejemplo, una utilidad para iniciar sesión, si se agota el espacio en el disco, simplemente puede detener el registro y no "bloquear" el programa principal con un error.
La ejecución de código asincrónico generalmente implica la operación de dicho código en un hilo separado. Esto es, si estamos hablando de un sistema con un procesador de un solo núcleo. En sistemas con procesadores multinúcleo, dicho código bien puede ejecutarse mediante un proceso que utilice un núcleo separado. Un procesador de un solo núcleo en un momento dado puede leer y ejecutar solo una instrucción. Es como leer libros. No puedes leer dos libros al mismo tiempo.
Si está leyendo un libro y alguien le está dando otro libro, puede tomar este segundo libro y comenzar a leerlo. Pero el primero tendrá que posponerse. La ejecución de código multiproceso se organiza según el mismo principio. Y si varias de sus copias leyeran varios libros a la vez, entonces sería similar a cómo funcionan los sistemas multiprocesador.
Si en un procesador de un solo núcleo es muy rápido cambiar entre tareas que requieren una potencia de cómputo diferente (por ejemplo, entre ciertos cálculos y leer datos de un disco), puede tener la sensación de que un solo núcleo de procesador hace varias cosas al mismo tiempo. O, digamos, esto sucede si intenta abrir varios sitios en un navegador a la vez. Si el navegador usa una secuencia separada para cargar cada una de las páginas, entonces todo se hará mucho más rápido que si estas páginas se cargaran una por una. La carga de la página no es una tarea tan difícil, no utiliza los recursos del sistema al máximo, como resultado, el lanzamiento simultáneo de varias de estas tareas es un movimiento muy efectivo.
Programación asincrónica de Python
Inicialmente, Python usó corutinas basadas en generador para resolver tareas de programación asincrónicas. Luego, en Python 3.4,
asyncio
módulo
asyncio
(a veces su nombre se escribe como
async IO
), que implementa mecanismos de programación asincrónica. Python 3.5 introdujo la construcción async / await.
Para realizar un desarrollo asincrónico en Python, debe lidiar con un par de conceptos. Estos son corutina y tarea.
Corutinas
Por lo general, la rutina es una función asincrónica. La rutina también puede ser un objeto devuelto por una función de rutina.
Si, al declarar una función, se indica que es asíncrona, puede llamarla con la palabra clave
await
:
await say_after(1, 'hello')
Tal construcción significa que el programa se ejecutará hasta que encuentre una expresión de espera, después de lo cual llamará a la función y pausará su ejecución hasta que se complete el trabajo de la función llamada. Después de eso, otras corutinas también podrán comenzar.
Pausar un programa significa que el control vuelve al bucle de eventos. Cuando se utiliza el módulo
asyncio
, el bucle de eventos realiza todas las tareas asincrónicas, realiza E / S y realiza subprocesos. En la mayoría de los casos, las tareas se utilizan para ejecutar corutina.
Las tareas
Las tareas le permiten ejecutar corutinas en un bucle de eventos. Esto simplifica el control de ejecución de varias corutinas. Aquí hay un ejemplo que usa corutinas y tareas. Tenga en cuenta que las entidades declaradas utilizando la construcción
async def
son corutinas. Este ejemplo está tomado de la
documentación oficial de Python.
import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): task1 = asyncio.create_task( say_after(1, 'hello')) task2 = asyncio.create_task( say_after(2, 'world')) print(f"started at {time.strftime('%X')}")
La función
say_after()
tiene el prefijo
async
; como resultado, tenemos la rutina. Si nos desviamos un poco de este ejemplo, podemos decir que esta función se puede llamar así:
await say_after(1, 'hello') await say_after(2, 'world')
Con este enfoque, sin embargo, las corutinas se invocan secuencialmente y tardan aproximadamente 3 segundos en completarse. En nuestro ejemplo, se lanzan competitivamente. Para cada uno de ellos se utiliza una tarea. Como resultado, el tiempo de ejecución de todo el programa es de aproximadamente 2 segundos. Tenga en cuenta que para que dicho programa funcione, no es suficiente simplemente declarar la función
main()
con la
async
. En tales situaciones, debe usar el módulo
asyncio
.
Si ejecuta el código de ejemplo, se mostrará un texto similar al siguiente en la pantalla:
started at 20:19:39 hello world finished at 20:19:41
Tenga en cuenta que las marcas de tiempo en la primera y última línea difieren en 2 segundos. Si ejecuta este ejemplo con una llamada secuencial de corutina, la diferencia entre las marcas de tiempo ya será de 3 segundos.
Ejemplo
En este ejemplo, se determina el número de operaciones requeridas para calcular la suma de diez elementos de una secuencia de números. Los cálculos se realizan a partir del final de la secuencia. Una función recursiva comienza obteniendo el número 10, luego se llama a sí mismo con los números 9 y 8, sumando lo que se devolverá. Esto continúa hasta que se completen los cálculos. Como resultado, resulta, por ejemplo, que la suma de una secuencia de números del 1 al 10 es 55. Al mismo tiempo, nuestra función es muy ineficiente, aquí se usa la construcción
time.sleep(0.1)
.
Aquí está el código de función:
import time def fib(n): global count count=count+1 time.sleep(0.1) if n > 1: return fib(n-1) + fib(n-2) return n start=time.time() global count count = 0 result = fib(10) print(result,count) print(time.time()-start)
¿Qué sucede si reescribe este código utilizando mecanismos asincrónicos y aplica la construcción
asyncio.gather
, que es responsable de realizar dos tareas y esperar a que se completen?
import asyncio,time async def fib(n): global count count=count+1 time.sleep(0.1) event_loop = asyncio.get_event_loop() if n > 1: task1 = asyncio.create_task(fib(n-1)) task2 = asyncio.create_task(fib(n-2)) await asyncio.gather(task1,task2) return task1.result()+task2.result() return n
De hecho, este ejemplo funciona incluso un poco más lento que el anterior, ya que todo se ejecuta en un hilo, y las llamadas a
create_task
,
gather
y otras similares crean una carga adicional en el sistema. Sin embargo, el propósito de este ejemplo es demostrar la capacidad de competir en múltiples tareas y esperar a que se completen.
Resumen
Hay situaciones en las que el uso de tareas y corutina es muy útil. Por ejemplo, si un programa contiene una combinación de entrada-salida y cálculos, o si se realizan cálculos diferentes en el mismo programa, puede resolver estos problemas ejecutando código de forma competitiva en lugar de en modo secuencial Esto ayuda a reducir el tiempo requerido para que el programa realice ciertas acciones. Sin embargo, esto no permite, por ejemplo, realizar cálculos simultáneamente. El multiprocesamiento se usa para organizar tales cálculos. Este es un gran tema aparte.
Estimados lectores! ¿Cómo se escribe el código asíncrono de Python?
