# Guía Completa: Concurrencia y Paralelismo en Python
## Optimiza tus Aplicaciones con Ejecución Simultánea
### Introducción
La concurrencia y el paralelismo son conceptos fundamentales para
crear aplicaciones eficientes que pueden manejar múltiples tareas
simultáneamente. Python ofrece varias herramientas para
implementar estos conceptos.
### Diferencias Clave
- **Concurrencia**: Maneja múltiples tareas progresando
simultáneamente
- **Paralelismo**: Ejecuta múltiples tareas exactamente al mismo
tiempo
- **GIL (Global Interpreter Lock)**: Limitación de Python que afecta el
paralelismo real
### Threading: Concurrencia con Hilos
#### 1. Uso Básico de Threads
```python
Import threading
Import time
Def tarea(nombre, tiempo_espera):
Print(f”Iniciando tarea {nombre}”)
Time.sleep(tiempo_espera)
Print(f”Completada tarea {nombre}”)
# Crear y ejecutar threads
Thread1 = threading.Thread(target=tarea, args=(“A”, 2))
Thread2 = threading.Thread(target=tarea, args=(“B”, 1))
Thread1.start()
Thread2.start()
Thread1.join()
Thread2.join()
```
#### 2. Sincronización con Locks
```python
Lock = threading.Lock()
Contador = 0
Def incrementar():
Global contador
With lock:
Actual = contador
Time.sleep(0.1) # Simula trabajo
Contador = actual + 1
# Uso seguro en múltiples threads
Threads = [threading.Thread(target=incrementar) for _ in range(5)]
For t in threads:
t.start()
for t in threads:
t.join()
```
### Multiprocessing: Paralelismo Real
#### 1. Procesos Básicos
```python
From multiprocessing import Process, Queue
Def procesar_datos(queue, datos):
Resultado = sum(datos)
Queue.put(resultado)
# Crear cola y procesos
If __name__ == ‘__main__’:
Queue = Queue()
Datos1 = list(range(1, 5000000))
Datos2 = list(range(5000000, 10000000))
P1 = Process(target=procesar_datos, args=(queue, datos1))
P2 = Process(target=procesar_datos, args=(queue, datos2))
P1.start()
P2.start()
Resultado1 = queue.get()
Resultado2 = queue.get()
P1.join()
P2.join()
Print(f”Resultado total: {resultado1 + resultado2}”)
```
#### 2. Pool de Procesos
```python
From multiprocessing import Pool
Def procesar_chunk(datos):
Return sum(x * x for x in datos)
If __name__ == ‘__main__’:
With Pool(processes=4) as pool:
Datos = list(range(1000000))
Chunks = [datos[i::4] for i in range(4)]
Resultados = pool.map(procesar_chunk, chunks)
Total = sum(resultados)
```
### AsyncIO: Programación Asíncrona
#### 1. Corrutinas Básicas
```python
Import asyncio
Async def tarea_asincrona(nombre, duracion):
Print(f”Iniciando {nombre}”)
Await asyncio.sleep(duracion)
Print(f”Completado {nombre}”)
Return f”Resultado de {nombre}”
Async def main():
# Ejecutar tareas concurrentemente
Resultados = await asyncio.gather(
Tarea_asincrona(“Tarea 1”, 2),
Tarea_asincrona(“Tarea 2”, 1),
Tarea_asincrona(“Tarea 3”, 3)
Print(resultados)
# Ejecutar el evento loop
Asyncio.run(main())
```
#### 2. Productores y Consumidores
```python
Async def productor(queue):
For i in range(5):
Await queue.put(f”item {i}”)
Await asyncio.sleep(1)
Await queue.put(None) # Señal de finalización
Async def consumidor(queue):
While True:
Item = await queue.get()
If item is None:
Break
Print(f”Procesando {item}”)
Await asyncio.sleep(0.5)
Async def main():
Queue = asyncio.Queue()
Await asyncio.gather(productor(queue), consumidor(queue))
```
### Patrones Avanzados
#### 1. Thread Pool Executor
```python
From concurrent.futures import ThreadPoolExecutor
Import requests
Def descargar_url(url):
Response = requests.get(url)
Return f”{url}: {len(response.content)} bytes”
Urls = [
http://example.com,
http://example.org,
http://example.net
With ThreadPoolExecutor(max_workers=3) as executor:
Resultados = list(executor.map(descargar_url, urls))
```
#### 2. Process Pool Executor
```python
From concurrent.futures import ProcessPoolExecutor
Import math
Def calcular_primos(n):
Return len([x for x in range(2, n)
If all(x % i ¡= 0 for i in range(2, int(math.sqrt(x)) + 1))])
Numeros = [100000, 200000, 300000, 400000]
With ProcessPoolExecutor(max_workers=4) as executor:
Resultados = list(executor.map(calcular_primos, numeros))
```
### Mejor Uso para Cada Caso
#### 1. Usar Threading para:
- Operaciones I/O intensivas
- Interfaces de usuario
- Operaciones de red
```python
Def descargar_archivos(urls):
Def descargar(url):
# Código de descarga aquí
Pass
Threads = []
For url in urls:
Thread = threading.Thread(target=descargar, args=(url,))
Threads.append(thread)
Thread.start()
For thread in threads:
Thread.join()
```
#### 2. Usar Multiprocessing para:
- Operaciones CPU intensivas
- Procesamiento de datos
- Cálculos complejos
```python
Def procesar_imagen(imagen):
# Procesamiento pesado aquí
Pass
If __name__ == ‘__main__’:
With ProcessPoolExecutor() as executor:
Imágenes = [‘img1.jpg’, ‘img2.jpg’, ‘img3.jpg’]
Resultados = executor.map(procesar_imagen, imágenes)
```
#### 3. Usar AsyncIO para:
- Servidores web
- Operaciones de red escalables
- Microservicios
```python
Async def servidor_web():
Async def manejar_cliente(reader, writer):
Data = await reader.read(100)
Writer.write(data)
Await writer.drain()
Writer.close()
Server = await asyncio.start_server(
Manejar_cliente, ‘127.0.0.1’, 8888)
```
### Mejores Prácticas
1. **Gestión de Recursos**
```python
# Usar context managers
With ThreadPoolExecutor() as executor:
# Código aquí
Pass
```
2. **Control de Errores**
```python
Async def manejar_errores():
Try:
Await operación_asincrona()
Except Exception as e:
Logging.error(f”Error: {e}”)
Finally:
Await limpiar_recursos()
```
3. **Monitoreo de Rendimiento**
```python
Import time
Def medir_tiempo(func):
Def wrapper(*args, **kwargs):
Inicio = time.time()
Resultado = func(*args, **kwargs)
Fin = time.time()
Print(f”Tiempo de ejecución: {fin – inicio} segundos”)
Return resultado
Return wrapper
```
### Conclusión
La elección entre concurrencia y paralelismo depende de tu caso de
uso específico. Threading es mejor para I/O, multiprocessing para
CPU, y asyncio para operaciones asíncronas. Comprende las
diferencias y elige la herramienta adecuada para cada tarea.