select en Python: monitorea varios I/O

Publicado el: 24/08/2026
Tempo de leitura: 6 minutos
Vibrant green tree python elegantly coiled on branch, showcasing its natural beauty.

El módulo select permite esperar hasta que sockets, pipes y otros descriptores estén listos para lectura, escritura o condiciones excepcionales. Esta técnica, llamada multiplexación de I/O, permite que un solo thread coordine muchas conexiones sin bloquearse en una sola.

Las primitivas cambian por sistema operativo: select() está ampliamente disponible, poll() aparece en Unix, epoll() es específico de Linux, kqueue() pertenece a BSD y macOS y /dev/poll existe en Solaris. Para aplicaciones portables, normalmente se prefiere selectors, que elige una implementación eficiente.

Disponibilidad no significa finalización

Un socket legible tiene alguna condición: datos, EOF o error. No garantiza que haya llegado un mensaje completo. Un socket escribible puede aceptar alguna salida, pero quizá solo una parte del buffer pendiente.

Los protocolos necesitan buffers de entrada y salida. Nunca supongas que un recv() devuelve un mensaje entero o que un send() transmite todos los bytes.

Servidor básico con select()

import select
import socket

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

lecturas = [servidor]

while True:
    listos, _, excepciones = select.select(lecturas, [], lecturas, 1.0)

    for sock in listos:
        if sock is servidor:
            cliente, direccion = servidor.accept()
            cliente.setblocking(False)
            lecturas.append(cliente)
        else:
            datos = sock.recv(4096)
            if datos:
                print(datos)
            else:
                lecturas.remove(sock)
                sock.close()

    for sock in excepciones:
        if sock in lecturas:
            lecturas.remove(sock)
        sock.close()

Es un ejemplo didáctico. Un servidor real necesita framing, buffers limitados, colas de salida, autenticación, timeouts y manejo robusto de excepciones. Consulta socketserver en Python para una abstracción de mayor nivel.

Las tres listas

La primera lista contiene objetos esperados para lectura. La segunda, objetos esperados para escritura. La tercera representa condiciones excepcionales definidas por la plataforma. Cada entrada puede ser un file descriptor entero o un objeto con fileno().

El resultado contiene tres subconjuntos listos. Un timeout None espera sin límite; cero consulta sin bloquear; un float positivo limita la espera en segundos.

Diferencias en Windows

En Windows, select() funciona solo con sockets de WinSock. Archivos normales, stdin y pipes ordinarios no se monitorean como en Unix. Programas portables pueden necesitar threads, asyncio o APIs específicas para fuentes que no sean sockets.

El módulo no está disponible en WASI.

Por qué selectors suele ser mejor

selectors.DefaultSelector elige epoll, kqueue, poll o select según la plataforma. Su modelo permite asociar datos a cada registro y oculta diferencias de bitmasks.

Usa select directamente cuando necesites flags del kernel, edge-trigger, eventos de procesos o archivos de kqueue o integración con código de bajo nivel existente.

Lecturas parciales y framing

TCP proporciona un flujo de bytes, no límites de mensajes. La aplicación puede definir delimitador, longitud prefijada, tamaño fijo o formato estructurado.

buffers = {}

def recibir(sock):
    bloque = sock.recv(4096)
    if not bloque:
        return False

    buffer = buffers.setdefault(sock, bytearray())
    buffer.extend(bloque)

    while b"\n" in buffer:
        linea, _, restante = buffer.partition(b"\n")
        buffer[:] = restante
        procesar_linea(linea)
    return True

Define un tamaño máximo. Un cliente que nunca envía delimitador no debe consumir memoria indefinidamente.

Escrituras parciales y backpressure

Monitorea escritura solo mientras exista salida pendiente. Los sockets conectados suelen estar escribibles, así que registrarlos permanentemente puede generar busy loop.

salidas = {}

def enviar_pendiente(sock):
    buffer = salidas.get(sock)
    if not buffer:
        return False

    enviados = sock.send(buffer)
    del buffer[:enviados]
    return bool(buffer)

Limita cada cola. Si un cliente lee lentamente, pausa productores, rechaza más datos, descarta mensajes según política o cierra la conexión. Ese control evita que un cliente agote la memoria.

EOF, half-close y errores

recv() devolviendo b"" indica que el peer cerró su lado de escritura. La aplicación quizá todavía tenga salida pendiente. poll() y epoll() exponen flags como POLLHUP, EPOLLHUP y EPOLLRDHUP.

Procesa errores y hangups aunque también exista lectura. Puede haber datos finales antes del cierre.

poll()

poll() registra descriptores con máscaras como POLLIN, POLLOUT, POLLERR, POLLHUP y POLLNVAL.

import select

poller = select.poll()
poller.register(sock, select.POLLIN)

for fd, evento in poller.poll(1000):
    if evento & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
        cerrar_fd(fd)
    elif evento & select.POLLIN:
        leer_fd(fd)

El timeout está en milisegundos. Mantén un mapping entre números y objetos de conexión.

epoll en Linux

epoll() escala bien con muchos descriptores y admite level-triggered y edge-triggered. En level-triggered el evento continúa mientras la condición sea verdadera. Con EPOLLET, las notificaciones indican cambios de estado.

Edge-trigger exige sockets no bloqueantes y leer o escribir hasta EAGAIN o EWOULDBLOCK. Parar antes puede dejar datos sin una nueva notificación.

import errno
import select

with select.epoll() as ep:
    ep.register(sock.fileno(), select.EPOLLIN | select.EPOLLET)
    for fd, evento in ep.poll(1.0):
        if evento & select.EPOLLIN:
            while True:
                try:
                    bloque = descriptores[fd].recv(65536)
                except BlockingIOError as exc:
                    if exc.errno in {errno.EAGAIN, errno.EWOULDBLOCK}:
                        break
                    raise
                if not bloque:
                    cerrar_fd(fd)
                    break
                procesar(bloque)

errno en Python explica esos códigos no bloqueantes.

EPOLLONESHOT y EPOLLEXCLUSIVE

EPOLLONESHOT desactiva un descriptor después de un evento hasta rearmarlo con modify(). Puede evitar que varios workers procesen la misma conexión. EPOLLEXCLUSIVE reduce el thundering herd cuando varios epoll esperan el mismo descriptor.

Son opciones específicas de Linux que requieren tests con los kernels usados.

kqueue

kqueue() monitorea I/O, procesos, señales, timers y eventos de vnode. Los cambios se representan mediante kevent. Flags como KQ_EV_ADD, KQ_EV_DELETE, KQ_EV_ONESHOT y KQ_EV_CLEAR controlan registros.

BSD y macOS pueden informar rename, delete, write y cambios de atributos en archivos. Ese mecanismo no es portable a Linux o Windows.

Integración con señales

El artículo anterior, signal en Python, presentó set_wakeup_fd(). Un pipe o socketpair no bloqueante puede despertar al selector cuando llega SIGTERM o SIGINT.

import os
import select
import signal

rfd, wfd = os.pipe()
os.set_blocking(rfd, False)
os.set_blocking(wfd, False)
signal.set_wakeup_fd(wfd)
signal.signal(signal.SIGTERM, lambda signum, frame: None)

while True:
    listos, _, _ = select.select([rfd, servidor], [], [], None)
    if rfd in listos:
        os.read(rfd, 4096)
        break

Drena el descriptor, restaura la configuración anterior y cierra ambos extremos. El handler permanece mínimo y el loop normal realiza el shutdown.

Timeouts y reloj monotónico

Los timeouts sirven para expiración, mantenimiento y deadlines. Calcula plazos con reloj monotónico.

import time

deadline = time.monotonic() + 30
while True:
    restante = max(0, deadline - time.monotonic())
    listos, _, _ = select.select(lecturas, [], [], restante)
    if time.monotonic() >= deadline:
        expirar_conexiones()

Descriptores cerrados y reutilizados

El sistema puede reutilizar números de descriptor. Desregistra antes de cerrar y elimina todo estado asociado. Un evento atrasado no debe aplicarse a una conexión nueva con el mismo número.

En epoll, unregister() puede mostrar EBADF después de un cierre prematuro. Centraliza el orden de limpieza y hazlo idempotente.

PIPE_BUF

En Unix, select.PIPE_BUF indica el mínimo que POSIX garantiza escribir atómicamente en un pipe marcado como escribible. No aplica a sockets ni garantiza tamaños arbitrarios.

Fairness

Un descriptor siempre activo puede monopolizar el loop. Limita bytes o mensajes procesados por evento y vuelve al multiplexor.

Mueve trabajo pesado de CPU a workers, procesos o executor. El thread de I/O debe mantenerse responsivo.

Seguridad y límites

Limita conexiones, bytes por cliente, tamaño de frame, cola de salida, tiempo ocioso, tasa y CPU. Multiplexar miles de sockets no crea memoria ilimitada.

TLS no bloqueante requiere cuidado: handshake y operaciones SSL pueden necesitar alternar lectura y escritura. Consulta ssl en Python.

Pruebas recomendadas

Prueba entrada fragmentada, salida parcial, cliente lento, cierre durante envío, half-close, timeout, señales durante espera, descriptor inválido, muchos sockets, backpressure, edge-trigger no drenado y diferencias Windows/Unix.

Usa socket.socketpair() y buffers pequeños para forzar operaciones parciales. Verifica que no haya busy loop ni crecimiento ilimitado.

Errores comunes

Los fallos frecuentes son tratar disponibilidad como mensaje completo, monitorear escritura siempre, ignorar envíos parciales, permitir buffers ilimitados, usar edge-trigger sin drenar hasta EAGAIN, cerrar antes de desregistrar, asumir archivos en Windows y ejecutar CPU pesada en el loop.

Conclusión

select da acceso directo a la multiplexación del sistema. Puede coordinar muchos descriptores con pocos threads si la aplicación gestiona buffers, backpressure, cierre y diferencias de plataforma.

Prefiere selectors para portabilidad, usa primitivas específicas solo cuando sean necesarias y mantén I/O no bloqueante. Consulta la documentación oficial de select y la documentación oficial de selectors.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Detailed view of programming code in a dark theme on a computer screen.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    signal en Python: cierre correcto

    Aprende signal en Python para manejar SIGTERM y SIGINT, detener servicios, usar timers, wakeup FD y evitar deadlocks en handlers.

    Ler mais

    Tempo de leitura: 7 minutos
    24/08/2026
    Creative concept showing the word 'error' with cut out letters on a table with scissors and paper.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    errno en Python: errores del sistema

    Aprende errno en Python para interpretar códigos del sistema, tratar OSError, archivos, red, retries y llamadas nativas de forma portable.

    Ler mais

    Tempo de leitura: 4 minutos
    24/08/2026
    African American man using a laptop in a well-organized library setting with colorful bookshelves.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ctypes en Python: usa bibliotecas C

    Aprende ctypes en Python para cargar bibliotecas C, definir tipos y punteros, gestionar memoria, callbacks, ABI y errores nativos.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    expat en Python: parser XML de bajo nivel

    Aprende expat en Python para parsing XML de bajo nivel, handlers, namespaces, diagnósticos y protección contra amplificación y DoS.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ElementInclude en Python: XInclude seguro

    Aprende ElementInclude en Python para usar XInclude con loaders seguros, base URL, profundidad máxima y bloqueo de rutas externas.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    xmlreader en Python: controla parsers SAX

    Aprende xmlreader en Python para configurar parsers SAX, InputSource, parsing incremental, atributos, locators y seguridad XML.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026