select no Python: monitore vários I/Os

Publicado em: 24/08/2026
Tempo de leitura: 7 minutos
Business professional analyzing financial data on multiple computer monitors at his workspace.

O módulo select permite esperar até que sockets, pipes e outros descritores estejam prontos para leitura, escrita ou condições excepcionais. Essa técnica, chamada multiplexação de I/O, permite que uma única thread acompanhe várias conexões sem bloquear em apenas uma delas.

A biblioteca expõe primitivas diferentes conforme o sistema operacional: select() está amplamente disponível, poll() aparece em Unix, epoll() é específico de Linux, kqueue() pertence a BSD e macOS, e /dev/poll existe em Solaris. Para aplicações portáveis, o módulo selectors geralmente é preferível, pois escolhe automaticamente uma implementação eficiente.

Prontidão não significa conclusão

Quando um socket aparece como legível, existe pelo menos alguma informação disponível: dados, EOF ou erro. Isso não significa que uma mensagem completa chegou. Quando aparece como gravável, uma escrita de algum tamanho provavelmente não bloqueará, mas o buffer pode aceitar apenas parte do conteúdo.

Protocolos precisam manter buffers de entrada e saída. Nunca presuma que um recv() retorna uma mensagem inteira ou que send() transmite todos os bytes.

Primeiro exemplo com 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)

leituras = [servidor]

while True:
    prontos, _, erros = select.select(leituras, [], leituras, 1.0)

    for sock in prontos:
        if sock is servidor:
            cliente, endereco = servidor.accept()
            cliente.setblocking(False)
            leituras.append(cliente)
        else:
            dados = sock.recv(4096)
            if dados:
                print(dados)
            else:
                leituras.remove(sock)
                sock.close()

    for sock in erros:
        if sock in leituras:
            leituras.remove(sock)
        sock.close()

Esse exemplo é didático. Um servidor real precisa de framing, buffers de saída, limites, timeouts, autenticação e tratamento de exceções. O guia de socketserver no Python mostra uma abstração mais pronta para servidores simples.

As três listas de select()

A primeira lista contém objetos aguardados para leitura. A segunda contém objetos aguardados para escrita. A terceira representa condições excepcionais definidas pela plataforma. Cada item pode ser um inteiro de file descriptor ou um objeto com método fileno().

O retorno contém três listas com subconjuntos prontos. Um timeout None bloqueia sem limite; um timeout zero apenas consulta e retorna imediatamente; um valor positivo limita a espera em segundos.

Diferenças no Windows

No Windows, select() funciona somente com sockets WinSock. Arquivos comuns, pipes e stdin não são aceitos como em Unix. Código multiplataforma deve testar o tipo de descritor e considerar threads, asyncio ou APIs específicas para entradas que não sejam sockets.

O módulo não está disponível em WASI. Não presuma que programas WebAssembly têm as mesmas primitivas de sistema.

Por que selectors costuma ser melhor

selectors.DefaultSelector escolhe epoll, kqueue, poll ou select conforme a plataforma. A API associa dados a cada registro e reduz diferenças de bitmasks.

Use select diretamente quando precisar de flags específicas do kernel, edge-trigger, eventos de processo do kqueue ou integração com código de sistema existente. Para servidores portáveis, prefira selectors.

Leituras parciais e framing

TCP é um fluxo de bytes, não um protocolo de mensagens. Uma aplicação pode usar delimitador, tamanho prefixado ou formato estruturado.

buffers = {}

def receber(sock):
    bloco = sock.recv(4096)
    if not bloco:
        return False

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

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

Imponha um limite máximo ao buffer. Um cliente que nunca envia delimitador não deve consumir memória indefinidamente.

Escritas parciais e backpressure

Registre um socket para escrita somente quando existir conteúdo pendente. Como sockets geralmente estão graváveis, monitorar todos o tempo todo cria loops ocupados.

saidas = {}

def enviar_pendente(sock):
    buffer = saidas.get(sock)
    if not buffer:
        return False

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

Defina um limite por conexão. Quando um cliente lê devagar, pare de aceitar novos dados para ele, descarte mensagens conforme a política ou encerre a conexão. Isso é backpressure.

EOF, half-close e erros

recv() retornando b"" indica que o peer fechou o lado de escrita. A aplicação pode ainda ter dados pendentes para enviar, dependendo do protocolo. Em poll() e epoll(), flags como POLLHUP, EPOLLHUP e EPOLLRDHUP ajudam a identificar fechamento.

Trate flags de erro e hangup mesmo quando também houver leitura. Alguns dados finais podem estar disponíveis antes do fechamento completo.

poll()

poll() registra descritores com bitmasks como POLLIN, POLLOUT, POLLERR, POLLHUP e 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):
        fechar(fd)
    elif evento & select.POLLIN:
        ler(fd)

O timeout de poll() é em milissegundos. Mantenha um mapping entre o número do descritor e o objeto associado.

epoll em Linux

epoll() escala bem com muitos descritores e pode trabalhar em modo level-triggered ou edge-triggered. No modo padrão level-triggered, o evento continua aparecendo enquanto a condição permanece verdadeira. Com EPOLLET, o kernel notifica mudanças de estado.

Edge-trigger exige sockets não bloqueantes e leitura ou escrita até receber EAGAIN/EWOULDBLOCK. Se o programa parar antes de drenar, pode não receber outro evento e a conexão ficará travada.

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:
                    bloco = descritores[fd].recv(65536)
                except BlockingIOError as exc:
                    if exc.errno in {errno.EAGAIN, errno.EWOULDBLOCK}:
                        break
                    raise
                if not bloco:
                    fechar(fd)
                    break
                processar(bloco)

O conjunto sobre errno no Python explica esses códigos não bloqueantes.

EPOLLONESHOT e EPOLLEXCLUSIVE

EPOLLONESHOT desativa internamente o descritor após um evento, exigindo rearm com modify(). Ele pode ajudar a impedir processamento concorrente do mesmo socket. EPOLLEXCLUSIVE reduz o thundering herd quando vários epolls aguardam o mesmo descritor.

Essas flags são específicas de Linux e precisam de testes com a versão real do kernel.

kqueue

kqueue() pode monitorar leitura, escrita, processos, sinais, timers e mudanças em arquivos. Eventos são descritos por objetos kevent. Flags como KQ_EV_ADD, KQ_EV_DELETE, KQ_EV_ONESHOT e KQ_EV_CLEAR alteram o comportamento.

Para observar mudanças de arquivos no BSD/macOS, filtros de vnode podem informar rename, delete, write e alterações de atributos. Isso não é portável para Windows ou Linux.

Integração com sinais

O artigo anterior, signal no Python, apresentou set_wakeup_fd(). Um pipe ou socketpair não bloqueante pode acordar o seletor quando SIGTERM ou SIGINT chega.

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:
    prontos, _, _ = select.select([rfd, servidor], [], [], None)
    if rfd in prontos:
        os.read(rfd, 4096)
        break

Faça cleanup do wakeup FD e restaure a configuração anterior. O handler continua mínimo; o shutdown ocorre dentro do loop normal.

Timeouts e relógio

Use o timeout do seletor para tarefas periódicas, expiração de conexões e shutdown. Calcule deadlines com relógio monotônico, não com horário de parede, que pode avançar ou retroceder.

import time

deadline = time.monotonic() + 30
while True:
    restante = max(0, deadline - time.monotonic())
    prontos, _, _ = select.select(leituras, [], [], restante)
    if time.monotonic() >= deadline:
        expirar()

Descritores fechados e reutilização

Números de file descriptor podem ser reutilizados pelo sistema. Remova o registro antes de fechar e elimine o objeto do mapping. Um evento atrasado associado a um número reutilizado não deve ser aplicado a uma nova conexão.

Em epoll, unregister() pode gerar EBADF quando o descritor já foi fechado. Centralize a ordem de fechamento e torne a limpeza idempotente.

PIPE_BUF

Em Unix, select.PIPE_BUF informa o mínimo garantido que pode ser escrito atomicamente em um pipe já indicado como gravável. A garantia não vale para sockets e não significa que qualquer tamanho será aceito.

Fairness

Um descritor sempre ocupado pode monopolizar o loop. Limite bytes ou mensagens processadas por evento e retorne à fila para permitir que outras conexões avancem.

Separe trabalho de CPU do loop de I/O. Processamento pesado deve ir para worker, processo ou executor, mantendo a thread do seletor responsiva.

Segurança e limites

Limite conexões simultâneas, bytes por conexão, tamanho de frame, filas de saída, tempo ocioso, taxa por cliente e custo de processamento. Multiplexar milhares de sockets não significa que o sistema possui memória ilimitada.

Para TLS, use sockets não bloqueantes com cuidado: handshake e operações SSL podem exigir alternância entre leitura e escrita. Veja ssl no Python.

Testes recomendados

Teste fragmentação de entrada, escrita parcial, cliente lento, fechamento durante envio, half-close, timeout, sinal durante espera, descritor inválido, muitos sockets, backpressure, edge-trigger não drenado e diferenças Windows/Unix.

Use socketpair para testes locais e simule buffers pequenos. Verifique ausência de busy loops e crescimento ilimitado de memória.

Erros comuns

Os erros mais frequentes são tratar prontidão como mensagem completa, registrar escrita o tempo inteiro, ignorar envios parciais, não limitar buffers, usar edge-trigger sem drenar até EAGAIN, fechar antes de desregistrar, presumir arquivos no Windows e fazer trabalho pesado no loop.

Conclusão

select oferece acesso direto às primitivas de multiplexação do sistema. Ele permite controlar muitos descritores com poucas threads, desde que a aplicação administre buffers, backpressure, fechamento e diferenças de plataforma.

Prefira selectors para portabilidade, use primitivas específicas somente quando necessário e mantenha todo I/O não bloqueante. Consulte a documentação oficial de select e a documentação oficial de selectors.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    View of multiple railway tracks with signals and buildings in an urban setting during daytime.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    signal no Python: encerre processos bem

    Aprenda signal no Python para tratar SIGTERM e SIGINT, encerrar serviços, usar timers, wakeup FD e coordenar shutdown sem deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    24/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    errno no Python: entenda erros do sistema

    Aprenda errno no Python para interpretar códigos do sistema, tratar OSError, rede, arquivos, retries e chamadas nativas de forma portátil.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ctypes no Python: use bibliotecas C

    Aprenda ctypes no Python para carregar bibliotecas C, definir tipos e ponteiros, gerenciar memória, callbacks, ABI e erros com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    expat no Python: parser XML de baixo nível

    Aprenda expat no Python para parsing XML de baixo nível, handlers, namespaces, erros e proteções contra amplificação e DoS.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ElementInclude no Python: use XInclude

    Aprenda ElementInclude no Python para usar XInclude com loaders seguros, base URL, profundidade máxima e bloqueio de caminhos externos.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    xmlreader no Python: controle parsers SAX

    Aprenda xmlreader no Python para configurar parsers SAX, InputSource, parsing incremental, atributos, locators e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026