1. Multiprocesamiento (tareas unidas a CPU):
- Cuándo usar: Ideal para tareas que son computacionalmente intensivas (unidas a la CPU), como el crujido de números, el procesamiento de imágenes o los cálculos complejos. Estas tareas se benefician más de la utilización de múltiples núcleos.
- Cómo funciona: Crea procesos separados, cada uno con su propio espacio de memoria. Esto evita las limitaciones de Lock de intérprete global (GIL), lo que permite la verdadera ejecución paralela.
- Ejemplo:
`` `Python
importar multiprocesamiento
tiempo de importación
Def process_item (elemento):
"" "Simula una tarea unida a CPU." ""
tiempo.sleep (1) # simular el trabajo
Artículo de devolución * 2
Def Main ()::
elementos =lista (rango (10))
start_time =time.time ()
con multiprocesamiento.pool (procesos =multiprocessing.cpu_count ()) como grupo:
Resultados =Pool.map (Process_item, elementos)
end_time =time.time ()
Imprimir (F "Resultados:{Resultados}")
print (f "Tiempo tomado:{end_time - start_time:.2f} segundos")
Si __name__ =="__main__":
principal()
`` `` ``
- Explicación:
- `multiprocesamiento.pool`:crea un grupo de procesos de trabajadores. `multiprocesamiento.cpu_count ()` Utiliza automáticamente el número de núcleos disponibles.
- `Pool.map`:aplica la función` Process_item` a cada elemento en la lista de `ítems ', distribuyendo el trabajo en los procesos de los trabajadores. Se maneja automáticamente la división del trabajo y recolectando los resultados.
- `Pool.apply_async`:una alternativa sin bloqueo a` Pool.map`. Debe recopilar resultados usando `resultado.get ()` para cada elemento.
- `Pool.imap` y` Pool.imap_unordered`:iteradores que devuelven los resultados a medida que están disponibles. `IMAP_UNORDERED` no garantiza el orden de los resultados.
- `Pool.starmap`:similar a` Pool.map`, pero le permite pasar múltiples argumentos a la función del trabajador utilizando tuplas.
- Ventajas:
- Superar la limitación de GIL para las tareas unidas a la CPU.
- Utiliza múltiples núcleos de CPU de manera eficiente.
- Desventajas:
- Overosto más alto que el enhebrado debido a la creación de procesos separados.
- La comunicación entre procesos (datos de aprobación) puede ser más lenta.
- Más memoria intensiva, ya que cada proceso tiene su propio espacio de memoria.
- Puede ser más complejo para administrar el estado compartido (necesita mecanismos de comunicación entre procesos como colas o memoria compartida).
2. Tasking (tareas de I/O):
- Cuándo usar: Adecuado para tareas que gastan una cantidad significativa de tiempo esperando operaciones externas (E/S con límites), como solicitudes de red, lecturas/escrituras de disco o consultas de bases de datos.
- Cómo funciona: Crea múltiples hilos dentro de un solo proceso. Los hilos comparten el mismo espacio de memoria. El GIL (Global Interpreter Lock) limita el verdadero paralelismo en Cpython, pero los hilos aún pueden mejorar el rendimiento al liberar el GIL cuando se espera E/S.
- Ejemplo:
`` `Python
Importación de hilo
tiempo de importación
def fetch_url (url):
"" "Simula una tarea de E/S." ""
print (f "obteniendo {url}")
Time.sleep (2) # Simular el retraso de la red
print (f "terminó de obtener {url}")
devolver f "contenido de {url}"
Def Main ()::
urls =["https://example.com/1", "https://example.com/2", "https://example.com/3"]
start_time =time.time ()
Hilos =[]
Resultados =[]
Para URL en URL:
Thread =Threading.Thread (Target =Lambda U:Results.append (fetch_url (u)), args =(url,)))
Threads.append (hilo)
Thread.Start ()
Para hilo en hilos:
Thread.Join () # espera a que se completen todos los hilos
end_time =time.time ()
Imprimir (F "Resultados:{Resultados}")
print (f "Tiempo tomado:{end_time - start_time:.2f} segundos")
Si __name__ =="__main__":
principal()
`` `` ``
- Explicación:
- `Threading.thread`:Crea un nuevo hilo.
- `thread.start ()`:inicia la ejecución del hilo.
- `thread.Join ()`:espera a que termine el hilo.
- Función lambda: Se usa para pasar la `URL` como un argumento a` Fetch_url` dentro del constructor `hilo '. Es esencial pasar el `URL` * por valor * para evitar condiciones de carrera donde todos los hilos puedan terminar usando el último valor de 'URL'.
- Ventajas:
- Bajo sobrecarga que el multiprocesamiento.
- Comparte el espacio de memoria, lo que facilita compartir datos entre hilos (pero requiere una sincronización cuidadosa).
- Puede mejorar el rendimiento de las tareas de E/S a pesar del GIL.
- Desventajas:
- El GIL limita el verdadero paralelismo para las tareas unidas a la CPU en Cpython.
- requiere una sincronización cuidadosa (bloqueos, semáforos) para evitar condiciones de carrera y corrupción de datos al acceder a recursos compartidos.
3. Asyncio (concurrencia con un solo hilo):
- Cuándo usar: Excelente para manejar grandes cantidades de tareas de E/S simultáneamente dentro de un solo hilo. Proporciona una forma de escribir un código asincrónico que pueda cambiar entre tareas mientras espera que las operaciones de E/S completen.
- Cómo funciona: Utiliza un bucle de eventos para administrar las coroutinas (funciones especiales declaradas con `Async Def`). Las coroutinas pueden suspender su ejecución mientras esperan E/S y permitir que se ejecuten otras coroutinas. `Asyncio` no * proporciona un verdadero paralelismo (es concurrencia), pero puede ser altamente eficiente para las operaciones unidas a E/S.
- Ejemplo:
`` `Python
importar asyncio
tiempo de importación
async def fetch_url (url):
"" "Simula una tarea de E/S (asíncrona)." "
print (f "obteniendo {url}")
espera asyncio.sleep (2) # simular retraso de red (no bloqueo)
print (f "terminó de obtener {url}")
devolver f "contenido de {url}"
async def main ()::
urls =["https://example.com/1", "https://example.com/2", "https://example.com/3"]
start_time =time.time ()
tareas =[fetch_url (URL) para URL en URL]
resultados =espera asyncio.gather (*tareas) # ejecutar tareas simultáneamente
end_time =time.time ()
Imprimir (F "Resultados:{Resultados}")
print (f "Tiempo tomado:{end_time - start_time:.2f} segundos")
Si __name__ =="__main__":
asyncio.run (main ())
`` `` ``
- Explicación:
- `Async def`:define una función asíncrona (coroutina).
- `` Await`:suspende la ejecución de la coroutina hasta que se complete la operación esperada. Libera el control al bucle de eventos, lo que permite que otras coroutinas se ejecuten.
- `Asyncio.sleep`:una versión asíncrona de` time.sleep` que no bloquea el bucle de eventos.
- `Asyncio.Gather`:ejecuta múltiples corutinas simultáneamente y devuelve una lista de sus resultados en el orden que fueron presentados. `*tareas" desempaquera la lista de tareas.
- `Asyncio.run`:inicia el bucle de eventos de Asyncio y ejecuta la coroutina 'Main`.
- Ventajas:
- Altamente eficiente para tareas de E/S, incluso con un solo hilo.
- Evita la sobrecarga de crear múltiples procesos o hilos.
- Más fácil de administrar la concurrencia que el enhebrado (menos necesidad de bloqueos explícitos).
- Excelente para construir aplicaciones de red altamente escalables.
- Desventajas:
- Requiere el uso de bibliotecas y código asincrónicos, que pueden ser más complejos de aprender y depurar que el código sincrónico.
- No es adecuado para tareas unidas a CPU (no proporciona un verdadero paralelismo).
- se basa en bibliotecas asincrónicas compatibles (por ejemplo, `aiohttp` en lugar de` solicitudes ').
4. Concurrente.futuras (abstracción sobre multiprocesamiento y roscado):
- Cuándo usar: Proporciona una interfaz de alto nivel para ejecutar tareas de forma asincrónica, utilizando hilos o procesos. Le permite cambiar entre roscado y multiprocesamiento sin cambiar significativamente su código.
- Cómo funciona: Utiliza `ThreadPoolExecutor` para subprocesos y` ProcesspoolExecutor` para multiprocesamiento.
- Ejemplo:
`` `Python
importación concurrente.
tiempo de importación
Def process_item (elemento):
"" "Simula una tarea unida a CPU." ""
tiempo.sleep (1) # simular el trabajo
Artículo de devolución * 2
Def Main ()::
elementos =lista (rango (10))
start_time =time.time ()
con concurrent.futures.processpoolexecutor (max_workers =multiprocessing.cpu_count ()) como ejecutor:
# Enviar cada artículo al Ejecutor
futures =[Ejecutor.submit (Process_item, item) para elementos en elementos]
# Espere a que todos los futuros se completen y obtengan los resultados
Resultados =[Future.Result () para el futuro en concurrente.futures.as_completed (futuros)]]
end_time =time.time ()
Imprimir (F "Resultados:{Resultados}")
print (f "Tiempo tomado:{end_time - start_time:.2f} segundos")
Si __name__ =="__main__":
importar multiprocesamiento
principal()
`` `` ``
- Explicación:
- `concurrente.futures.processpoolexecutor`:crea un grupo de procesos de trabajadores. También puede usar `concurrent.futures.threadpoolexecutor` para hilos.
- `Ejecutor.submit`:envía una llamada (función) al ejecutor para la ejecución asíncrona. Devuelve un objeto 'futuro' que represente el resultado de la ejecución.
- `concurrent.futures.as_completed`:un iterador que produce objetos 'futuro` a medida que se completan, sin ningún orden particular.
- `Future.Result ()`:Recupera el resultado del cálculo asincrónico. Bloqueará hasta que el resultado esté disponible.
- Ventajas:
- Interfaz de alto nivel, simplificando la programación asincrónica.
- Cambie fácilmente entre hilos y procesos cambiando el tipo de ejecutor.
- Proporciona una forma conveniente de administrar tareas asincrónicas y recuperar sus resultados.
- Desventajas:
- Puede tener un poco más de sobrecarga que usar `multiprocesamiento` o` enhebring` directamente.
Elegir el enfoque correcto:
| Enfoque | Tipo de tarea | Limitación de Gil | Uso de la memoria | Complejidad |
| ------------------- | ------------------- | ---------------- | --------------- | ------------ |
| Multiprocesamiento | CPU-TOMBUSTIBLE | Superado | Alto | Moderado |
| Hilo | I/o-unido | SÍ | Bajo | Moderado |
| Asyncio | I/o-unido | SÍ | Bajo | Alto |
| Concurrente. FUTURAS | Ambos | Depende | Varía | Bajo |
Consideraciones clave:
* Tipo de tarea (unido a CPU vs. I/O-unido): Este es el factor más importante. Las tareas unidas a la CPU se benefician del multiprocesamiento, mientras que las tareas de E/S son más adecuadas para el enhebrado o Asyncio.
* gil (bloqueo de intérprete global): El Gil en Cpython limita el verdadero paralelismo en el enhebrado. Si necesita un verdadero paralelismo para las tareas unidas a CPU, use el multiprocesamiento.
* Overhead: El multiprocesamiento tiene una sobrecarga más alta que el enhebrado y el asyncio.
* Uso de la memoria: El multiprocesamiento usa más memoria porque cada proceso tiene su propio espacio de memoria.
* Complejidad: Asyncio puede ser más complejo de aprender que el enhebrado o el multiprocesamiento.
* Compartir datos: Compartir datos entre procesos (multiprocesamiento) requiere mecanismos de comunicación entre procesos (colas, memoria compartida), lo que puede agregar complejidad. Los hilos comparten espacio de memoria, pero requieren una sincronización cuidadosa para evitar condiciones de carrera.
* Soporte de la biblioteca: Asegúrese de que las bibliotecas que está utilizando sean compatibles con Asyncio si elige ese enfoque. Muchas bibliotecas ahora ofrecen versiones asincrónicas (por ejemplo, `Aiohttp` para solicitudes HTTP).
Las mejores prácticas:
* Perfre su código: Antes de implementar el paralelismo, perfile su código para identificar los cuellos de botella. No optimice prematuramente.
* Medir rendimiento: Pruebe diferentes enfoques y mida su rendimiento para determinar cuál funciona mejor para su caso de uso específico.
* Mantenga las tareas independientes: Cuanto más independientes sean sus tareas, más fácil será paralelizarlas.
* manejar excepciones: Maneje correctamente las excepciones en las funciones o corutinas de su trabajador para evitar que se bloqueen toda la aplicación.
* Use colas para la comunicación: Si necesita comunicarse entre procesos o hilos, use colas para evitar condiciones de carrera y garantizar la seguridad de los hilos.
* Considere una cola de mensajes: Para sistemas distribuidos complejos, considere usar una cola de mensajes (por ejemplo, RabbitMQ, Kafka) para el procesamiento de tareas asincrónicas.
Al considerar cuidadosamente estos factores, puede elegir el enfoque más eficiente para ejecutar su bucle de ejecución de Python en paralelo y mejorar significativamente el rendimiento de su aplicación. Recuerde probar y medir los resultados para garantizar que su enfoque elegido realmente esté proporcionando un beneficio de rendimiento.