Conceptos básicos de concurrencia
9 ejemplos para empezar con la concurrencia: 6 básicos y 3 intermedios.
Requisitos previos
- Tener instalado Python 3.14.0.
- Leer Elección de un modelo de concurrencia para obtener orientación sobre I/O frente a CPU.
Ejemplos básicos
1. Concurrencia frente a paralelismo
La concurrencia entrelaza tareas; el paralelismo ejecuta tareas simultáneamente en múltiples núcleos.
# Concurrente: un hilo cambia mientras otro espera en I/O
# Paralelo: dos procesos en dos núcleos de CPU calculan a la vez- El trabajo limitado por I/O se beneficia de la concurrencia (hilos/async).
- El trabajo limitado por CPU necesita paralelismo (procesos) debido al GIL.
- Async es concurrente pero generalmente de un solo hilo.
Relacionado: El GIL y Python con hilos libres - Detalles del GIL
2. Hilo para espera de I/O
Los hilos superponen la espera de I/O bloqueante.
import time
from threading import Thread
def fetch(_id: int) -> None:
time.sleep(0.1) # sustituto de la red
print("done", _id)
threads = [Thread(target=fetch, args=(i,)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()sleepsimula la liberación del GIL por I/O bloqueante.joinasegura que todo el trabajo se complete antes de salir.- Los hilos comparten memoria: proteja el estado mutable con bloqueos (
locks).
Relacionado: threading - bloqueos y seguridad
3. Proceso para trabajo de CPU
Los procesos evitan el GIL con intérpretes separados.
from multiprocessing import Pool
def square(n: int) -> int:
return n * n
if __name__ == "__main__":
with Pool(2) as pool:
print(pool.map(square, range(4)))- La guarda
if __name__ == "__main__"es necesaria en plataformas de creación (spawn). - Los procesos no comparten memoria por defecto.
- El costo de inicio es mayor que el de los hilos: agrupe el trabajo.
Relacionado: multiprocessing - pools y IPC
4. ThreadPool de concurrent.futures
API de pool de alto nivel para hilos y procesos.
from concurrent.futures import ThreadPoolExecutor, as_completed
def work(n: int) -> int:
return n * 2
with ThreadPoolExecutor(max_workers=4) as ex:
futures = [ex.submit(work, i) for i in range(6)]
for fut in as_completed(futures):
print(fut.result())submitdevuelve unFuture;mappreserva el orden.as_completedproduce resultados a medida que cada uno termina.- El gestor de contexto cierra los trabajadores limpiamente.
Relacionado: concurrent.futures - API completa
5. Coordinación con queue.Queue
Productor/consumidor desacopla las etapas.
from queue import Queue
from threading import Thread
q: Queue[int] = Queue()
def producer() -> None:
for i in range(3):
q.put(i)
q.put(-1) # centinela
def consumer() -> None:
while True:
item = q.get()
if item == -1:
break
print("got", item)
Thread(target=producer).start()
Thread(target=consumer).start()Queuees seguro para hilos y bloqueante.- El centinela finaliza el bucle del consumidor limpiamente.
- Prefiera
join()en la cola para canalizaciones de producción.
Relacionado: Colas y Productor/Consumidor - canalizaciones
6. Riesgo de estado compartido
Los incrementos sin protección compiten (race).
import threading
counter = 0
def inc() -> None:
global counter
for _ in range(100_000):
counter += 1
threads = [threading.Thread(target=inc) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # a menudo < 200_000counter += 1es una operación de lectura-modificación-escritura, no atómica en el bytecode de Python.- Use
threading.Locko tipos atómicos para contadores compartidos. - Prefiera el paso de mensajes sobre el estado mutable compartido.
Relacionado: Mejores prácticas de concurrencia - evitar carreras (
races)
Ejemplos intermedios
7. I/O concurrente con asyncio
Bucle de eventos de un solo hilo con muchas tareas awaiting.
import asyncio
async def fetch(n: int) -> int:
await asyncio.sleep(0.1)
return n
async def main() -> None:
results = await asyncio.gather(*(fetch(i) for i in range(3)))
print(results)
asyncio.run(main())asyncioes adecuado para muchas esperas de I/O concurrentes.- No paraleliza Python limitado por CPU en un solo hilo.
- Consulte la sección de asyncio para patrones de producción.
Relacionado: Conceptos básicos de Asyncio - bucle de eventos
8. Vista previa de subintérpretes
Intérpretes aislados que reducen la contención del GIL (APIs experimentales a partir de 3.12).
# Conceptual: use el módulo interpreters cuando esté habilitado para su carga de trabajo
# Cada subintérprete puede poseer un GIL en compilaciones con hilos libres
print("evaluar subintérpretes para aislamiento de CPU en 3.13+")- Los subintérpretes apuntan al paralelismo de CPU sin la sobrecarga completa de los procesos.
- Las APIs evolucionan: lea la página dedicada antes de usar en producción.
- No es un reemplazo directo para todos los casos de multiprocessing.
Relacionado: Subintérpretes - cuándo adoptar
9. Lista de verificación para elegir modelo
Decisión rápida antes de importar una biblioteca.
def model_hint(bound: str) -> str:
if bound == "io":
return "asyncio o hilos"
if bound == "cpu":
return "multiprocessing o subprocess"
return "perfilar primero"- Perfile antes de cambiar de modelo: las suposiciones pueden ser erróneas.
- Las cargas de trabajo mixtas dividen la capa de I/O async y el pool de procesos para CPU.
- Documente la elección en un ADR para la alineación del equipo.
Relacionado: Elección de un modelo de concurrencia - lista de verificación completa
Versiones de la pila: Esta página se escribió para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ y uv 0.6+.