selectors en Python: I/O no bloqueante

Publicado el: 11/08/2026
Tempo de leitura: 4 minutos
Red de conexiones que representa I/O no bloqueante con selectors en Python

El módulo selectors ofrece una interfaz de alto nivel para esperar eventos de entrada y salida en muchos descriptores. Se usa principalmente con sockets no bloqueantes. En lugar de crear una thread por conexión, un único loop puede reaccionar solamente cuando cada recurso está preparado para leer o escribir.

El módulo envuelve primitivas de bajo nivel como select, poll, epoll y kqueue. DefaultSelector elige automáticamente la implementación más eficiente disponible.

Cuándo usar selectors

Usa selectors cuando necesites control directo sobre un loop de eventos, implementar un protocolo sencillo o monitorizar muchos sockets sin un framework completo. Para aplicaciones asíncronas habituales, asyncio suele ser más productivo.

El módulo exige gestionar buffers, mensajes parciales, desconexiones, backpressure y plazos. Una operación bloqueante de base de datos o disco sigue bloqueando todo el loop.

Crear el selector por defecto

import selectors

with selectors.DefaultSelector() as selector:
    print(type(selector).__name__)

El context manager libera el recurso interno. Después de close(), el selector no puede reutilizarse.

Eventos de lectura y escritura

Las máscaras principales son EVENT_READ y EVENT_WRITE. Se combinan con OR bit a bit.

eventos = selectors.EVENT_READ | selectors.EVENT_WRITE

Preparado para lectura también puede significar EOF o cierre del peer. Preparado para escritura indica que el sistema acepta alguna cantidad de datos, no necesariamente todo el buffer.

Registrar un socket servidor

import socket
import selectors

selector = selectors.DefaultSelector()
servidor = socket.socket()
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servidor.bind(("127.0.0.1", 9000))
servidor.listen()
servidor.setblocking(False)

selector.register(
    servidor,
    selectors.EVENT_READ,
    data={"tipo": "servidor"},
)

register() asocia objeto, máscara y datos opcionales. Los datos pueden ser un callback, ID de sesión u objeto de estado.

Aceptar conexiones

def aceptar(sock, selector):
    conexion, direccion = sock.accept()
    conexion.setblocking(False)
    estado = {
        "direccion": direccion,
        "entrada": bytearray(),
        "salida": bytearray(),
    }
    selector.register(conexion, selectors.EVENT_READ, estado)

El socket aceptado también debe ser no bloqueante. Elimínalo del selector antes de cerrarlo.

El loop de eventos

while True:
    listos = selector.select(timeout=1.0)
    for clave, mascara in listos:
        if clave.fileobj is servidor:
            aceptar(servidor, selector)
        else:
            procesar(clave, mascara, selector)

select() devuelve pares de SelectorKey y máscara lista. La clave contiene objeto, descriptor, eventos solicitados y datos asociados.

Timeouts

Con timeout=None, la espera es indefinida. Un valor positivo limita segundos. Cero o negativo consulta sin bloquear.

Este timeout no sustituye el plazo del protocolo. Guarda la última actividad y desconecta clientes inactivos según la política de la aplicación.

Recibir sin bloquear

def recibir(sock, estado):
    try:
        datos = sock.recv(4096)
    except BlockingIOError:
        return

    if datos:
        estado["entrada"].extend(datos)
    else:
        raise ConnectionResetError("cliente cerrado")

Incluso después de una notificación, trata BlockingIOError. Un resultado vacío normalmente indica cierre ordenado.

TCP entrega un flujo

Una llamada puede devolver media trama, varias tramas o cualquier división. El protocolo debe definir framing mediante delimitador, longitud prefijada o formato autodescriptivo.

def extraer_lineas(buffer):
    lineas = []
    while b"\n" in buffer:
        linea, _, restante = buffer.partition(b"\n")
        lineas.append(linea)
        buffer[:] = restante
    return lineas

Impone un tamaño máximo para impedir consumo ilimitado de memoria.

Escritura parcial y backpressure

def enviar(sock, estado):
    if not estado["salida"]:
        return
    try:
        enviados = sock.send(estado["salida"])
    except BlockingIOError:
        return
    del estado["salida"][:enviados]

send() puede enviar menos bytes. Conserva el resto. Monitoriza EVENT_WRITE solo cuando existan datos pendientes; hacerlo siempre puede producir un loop ocupado.

Modificar intereses

def actualizar(sock, estado, selector):
    eventos = selectors.EVENT_READ
    if estado["salida"]:
        eventos |= selectors.EVENT_WRITE
    selector.modify(sock, eventos, estado)

modify() es más eficiente que eliminar y registrar de nuevo.

Cerrar correctamente

def cerrar(sock, selector):
    try:
        selector.unregister(sock)
    except KeyError:
        pass
    sock.close()

Trata errores por conexión para que un cliente defectuoso no detenga todo el servidor.

Diferencias entre plataformas

En Windows, los selectores soportan principalmente sockets; pipes normales no funcionan igual que en Unix. En Unix pueden soportarse sockets, pipes, FIFOs y dispositivos especiales según la implementación.

DefaultSelector mejora portabilidad, pero no hace que todos los objetos sean seleccionables. El módulo no está disponible en WASI.

Objetos de sesión

Para protocolos mayores, usa una dataclass con buffers, dirección, plazos, estado del parser y métricas.

from dataclasses import dataclass, field

@dataclass
class Sesion:
    direccion: tuple
    entrada: bytearray = field(default_factory=bytearray)
    salida: bytearray = field(default_factory=bytearray)
    cerrando: bool = False

Mantén límites y no conserves secretos innecesarios.

Señales y resultados vacíos

Python moderno normalmente repite la espera cuando una señal interrumpe y el handler no genera excepción. Aun así, el loop debe aceptar una lista vacía y ejecutar mantenimiento periódico.

Evitar CPU alta

Un loop puede consumir un núcleo completo si siempre monitoriza escritura, usa timeout cero o gira tras un error. Registra solo eventos útiles y elige un timeout razonable.

Mide cantidad de eventos, tamaño de buffers, duración de callbacks y latencia del loop. Un callback lento retrasa todas las conexiones.

Mover trabajo bloqueante

El parsing pequeño puede quedarse en el loop. CPU intensiva, consultas bloqueantes y disco lento deben ejecutarse en otro worker. Devuelve resultados mediante una cola segura o descriptor de wakeup.

Ejemplo de procesamiento

def procesar(clave, mascara, selector):
    sock = clave.fileobj
    estado = clave.data
    try:
        if mascara & selectors.EVENT_READ:
            recibir(sock, estado)
            for linea in extraer_lineas(estado["entrada"]):
                estado["salida"].extend(linea.upper() + b"\n")

        if mascara & selectors.EVENT_WRITE:
            enviar(sock, estado)

        actualizar(sock, estado, selector)
    except (ConnectionError, OSError):
        cerrar(sock, selector)

Producción requiere validación, límites, logs, cierre ordenado y cancelación.

selectors frente a asyncio

asyncio usa conceptos similares y puede utilizar selectores internamente. Elige selectors para control de bajo nivel. Elige asyncio para coroutines, tareas, streams y ecosistema asíncrono.

Errores frecuentes

  • Olvidar setblocking(False).
  • Cerrar antes de hacer unregister.
  • Suponer que un recv devuelve una trama completa.
  • Ignorar escrituras parciales.
  • Monitorizar escritura permanentemente.
  • Ejecutar trabajo bloqueante en el loop.
  • Suponer soporte idéntico de pipes.

Buenas prácticas

  • Usa DefaultSelector.
  • Limita buffers por cliente.
  • Registra escritura solo cuando sea necesaria.
  • Trata errores por conexión.
  • Implementa plazos del protocolo.
  • Cierra sockets y selector de forma fiable.
  • Prueba mensajes fragmentados, clientes lentos y desconexiones.

Guías relacionadas

Continúa con contextvars en Python, ExitStack en Python, shlex en Python, faulthandler en Python y platform en Python.

Consulta la documentación oficial de selectors y la documentación de socket.

Conclusión

selectors permite construir loops de I/O eficientes y portables con control directo sobre lectura, escritura y estado de conexión. La fiabilidad depende de buffers limitados, backpressure, plazos, errores por cliente y ausencia de operaciones bloqueantes.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Flujo de datos en red que representa contexto asíncrono con contextvars en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextvars en Python: contexto asíncrono

    Aprende contextvars en Python para guardar estado por tarea, evitar fugas en asyncio, copiar contextos y restaurar valores con tokens.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Código de programación que representa operaciones como funciones con operator en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    operator en Python: operaciones como funciones

    Aprende operator en Python para usar operaciones como funciones, ordenar campos, acceder a elementos, llamar métodos y crear pipelines claros.

    Ler mais

    Tempo de leitura: 4 minutos
    11/08/2026
    Alfabeto tridimensional que representa normalización Unicode con unicodedata en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    unicodedata en Python: normaliza Unicode

    Aprende unicodedata en Python para normalizar Unicode, consultar nombres, categorías, números, marcas combinantes y ancho de visualización.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Red de servidores que representa la gestión de recursos con ExitStack en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ExitStack en Python: gestiona recursos

    Aprende ExitStack en Python para gestionar archivos, conexiones, callbacks y limpieza dinámica con seguridad y orden predecible.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Carpeta con candado que representa tipos y permisos con stat en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    stat en Python: tipos y permisos

    Aprende stat en Python para interpretar tipos de archivo, permisos, enlaces, timestamps, atributos de Windows y flags de Unix con

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Portátil con código que representa documentación automática con pydoc en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pydoc en Python: documentación automática

    Aprende pydoc en Python para generar ayuda en terminal, HTML, búsqueda y un servidor local de documentación de forma segura.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026