Diferencia en aspectos de concurrencia en Python

Diferencia en aspectos de concurrencia en Python

Uno puede imaginar que Python acaba de introducir estas nociones o capacidades, dado que estamos escuchando muchas tendencias nuevas con respecto a las operaciones asincrónicas y la concurrencia con el lanzamiento de Python 3.

Muchos recién llegados podrían creer que usar asyncio es el único enfoque práctico para realizar actividades concurrentes y asíncronas. Este artículo discutirá cómo podemos lograr la concurrencia y sus ventajas o desventajas en Python.

Hilos y multihilos

Los hilos han estado en Python durante mucho tiempo. Como resultado, podemos realizar múltiples operaciones a la vez gracias a los hilos.

Desafortunadamente, CPython, una versión típica de Python de la línea principal, todavía usa el bloqueo de intérprete global (GIL), lo que hace que las aplicaciones de subprocesos múltiples, el método común de hoy en día para implementar el procesamiento paralelo, no sean ideales.

Python introdujo GIL para hacer que la memoria de CPython maneje integraciones más manejables con C (por ejemplo, las extensiones).

El GIL es un mecanismo de bloqueo en el que el intérprete de Python ejecuta solo un hilo simultáneamente. El código byte de Python solo puede ser ejecutado por un subproceso simultáneamente.

Código de ejemplo:

import threading
import time
import random


def worker(num):
    sec = random.randrange(1, 5)
    time.sleep(sec)
    print("I am thread {}, who slept for {} seconds.".format(num, sec))


for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    t.start()

print("Completed!")

Producción :

Completed!
I am thread 1, who slept for 3 seconds.
I am thread 3, who slept for 2 seconds.
I am thread 4, who slept for 4 seconds.

Procesos y Multiprocesamiento

El multiprocesamiento hace uso de muchas CPU. Podemos realizar varias tareas simultáneamente de manera efectiva ya que cada CPU opera en paralelo. Para los trabajos que están vinculados a la CPU, el multiprocesamiento es lo que desea utilizar.

Python introduce el módulo de multiprocesamiento para lograr el paralelismo, que se sentirá muy similar si ha utilizado subprocesos.

Código de ejemplo:

import multiprocessing
import time
import random


def worker(num):
    sec = random.randrange(1, 5)
    time.sleep(sec)
    print("I am process {}, who slept for {} seconds.".format(num, sec))


for i in range(3):
    t = multiprocessing.Process(target=worker, args=(i,))
    t.start()

print("Completed")

Producción :

Completed
I am process 1, who slept for 1 seconds.
I am process 2, who slept for 2 seconds.
I am process 0, who slept for 3 seconds.

En lugar de subprocesos múltiples, usamos múltiples procesos que se ejecutan en diferentes núcleos de su CPU, lo que hace que nuestro script de Python sea más rápido.

Asíncrono y asyncio

En las operaciones síncronas, las tareas se realizan de forma sincronizada, una tras otra. Sin embargo, los trabajos pueden comenzar en operaciones asincrónicas con total independencia entre sí.

Una tarea asincrónica puede comenzar y continuar ejecutándose mientras la ejecución cambia a otra actividad. Por otro lado, las tareas asincrónicas a menudo se ejecutan en segundo plano y no se bloquean (hacen que la implementación espere a que se complete).

Junto con otras características valiosas, asyncio ofrece un ciclo de eventos. El bucle de eventos supervisa varios eventos de E/S, cambia a tareas preparadas y pausa las tareas que esperan E/S.

Como resultado, no perdemos el tiempo en proyectos sin terminar.

Código de ejemplo:

import asyncio
import datetime
import random


async def my_sleep_func():
    await asyncio.sleep(random.randint(0, 5))


async def displayDate(num, loop):
    endTime = loop.time() + 60.0
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= endTime:
            break
        await my_sleep_func()


loop = asyncio.get_event_loop()

asyncio.ensure_future(displayDate(1, loop))
asyncio.ensure_future(displayDate(2, loop))

loop.run_forever()

Si repasamos el fragmento de código anterior:

  • Tenemos una función async displayDate, que toma como parámetros un número y el bucle de eventos.
  • Dicha función tiene un bucle infinito que se detiene después de 60 segundos. Pero durante estos 60 segundos, imprime repetidamente la hora y toma una siesta.
  • La función await puede esperar a que se completen otras funciones async.
  • Pasamos la función al bucle de eventos (usando la función ensure_future).
  • Comenzamos a ejecutar el bucle de eventos.

Cada vez que se realiza la llamada await, asyncio entiende que la función probablemente necesitará algo de tiempo. Cuando asyncio nota que la E/S de la función detenida está lista, reanuda el proceso.

Ahora, el punto es, ¿qué necesitamos usar entre las tres formas de concurrencia? Podemos tomar nota de lo siguiente para ayudarnos en nuestra toma de decisiones:

  • Utilice el multiprocesamiento para las operaciones vinculadas a la CPU.
  • Utilice subprocesos múltiples para E/S limitada, E/S rápida y número limitado de conexiones.
  • Use E/S asíncrona para E/S limitada, E/S lenta y muchas conexiones.
  • asyncio/await funciona en Python 3.5 y versiones posteriores.

También podemos referirnos al pseudocódigo a continuación:

if io_bound:
    if io_very_slow:
        print("Use asyncio")
    else:
        print("Use multithreading")
else:
    print("multiprocessing")
Marion Paul Kenneth Mendoza avatar Marion Paul Kenneth Mendoza avatar

Marion specializes in anything Microsoft-related and always tries to work and apply code in an IT infrastructure.

LinkedIn

Artículo relacionado - Python Threading

Artículo relacionado - Python Async