selectors no Python: I/O não bloqueante

Publicado em: 11/08/2026
Tempo de leitura: 5 minutos
Rede de conexões representando I/O não bloqueante com selectors no Python

O módulo selectors oferece uma interface de alto nível para esperar eventos de entrada e saída em vários descritores ao mesmo tempo. Ele é usado principalmente com sockets não bloqueantes, mas em sistemas Unix também pode trabalhar com pipes, FIFOs e alguns dispositivos. Em vez de criar uma thread por conexão, um único loop pode reagir apenas quando cada recurso estiver pronto para leitura ou escrita.

O módulo encapsula primitivas de baixo nível como select, poll, epoll e kqueue. Para a maioria das aplicações, DefaultSelector escolhe automaticamente a implementação mais eficiente disponível.

Quando usar selectors

Use selectors quando você precisa controlar diretamente um loop de eventos, integrar protocolos simples, monitorar muitos sockets ou construir uma camada de rede sem depender de um framework completo. Para aplicações assíncronas comuns, asyncio normalmente oferece uma abstração mais produtiva.

O módulo exige que o código administre buffers, estados parciais, desconexões, timeouts e backpressure. Ele é poderoso, mas não transforma automaticamente uma operação bloqueante em não bloqueante.

Criar o seletor padrão

import selectors

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

O context manager garante a liberação do recurso interno. Depois de close(), o seletor não pode ser reutilizado.

Eventos de leitura e escrita

Há duas máscaras principais: EVENT_READ e EVENT_WRITE. Elas podem ser combinadas com o operador bit a bit |.

eventos = selectors.EVENT_READ | selectors.EVENT_WRITE

“Pronto para leitura” não significa necessariamente que haverá dados úteis. Em um socket, também pode indicar EOF ou uma desconexão. “Pronto para escrita” indica que o sistema aceita algum envio naquele momento, não que um buffer inteiro será enviado de uma vez.

Registrar um socket

import socket
import selectors

seletor = 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)

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

register() associa o objeto, os eventos monitorados e um campo data arbitrário. Esse campo pode guardar um callback, ID de sessão ou objeto de estado.

Aceitar conexões

def aceitar(sock, seletor):
    conexao, endereco = sock.accept()
    conexao.setblocking(False)
    estado = {
        "endereco": endereco,
        "entrada": bytearray(),
        "saida": bytearray(),
    }
    seletor.register(conexao, selectors.EVENT_READ, estado)

O novo socket também precisa ser não bloqueante. Uma conexão deve ser removida do seletor antes de ser fechada.

O loop de eventos

while True:
    eventos = seletor.select(timeout=1.0)
    for chave, mascara in eventos:
        if chave.fileobj is servidor:
            aceitar(servidor, seletor)
        else:
            processar(chave, mascara, seletor)

select() devolve pares (SelectorKey, events). O SelectorKey contém o objeto registrado, descritor, máscara solicitada e dados associados.

Timeouts

Com timeout=None, a chamada espera indefinidamente. Um valor positivo limita a espera em segundos. Zero ou um valor negativo apenas consulta os eventos já prontos.

O timeout do seletor não substitui timeouts de protocolo. Para encerrar clientes inativos, registre o último horário de atividade e verifique-o periodicamente.

Receber dados sem bloquear

def receber(sock, estado):
    try:
        dados = sock.recv(4096)
    except BlockingIOError:
        return

    if dados:
        estado["entrada"].extend(dados)
    else:
        raise ConnectionResetError("cliente encerrou")

Mesmo após um evento, trate BlockingIOError, pois o estado pode mudar entre a notificação e a chamada. Um retorno vazio de recv() normalmente indica fechamento ordenado do outro lado.

Buffers e mensagens parciais

TCP é um fluxo de bytes. Uma chamada pode devolver metade de uma mensagem, várias mensagens ou qualquer divisão. O protocolo precisa definir enquadramento, como tamanho prefixado, delimitador ou formato autodescritivo.

def extrair_linhas(buffer):
    linhas = []
    while b"\n" in buffer:
        linha, _, restante = buffer.partition(b"\n")
        linhas.append(linha)
        buffer[:] = restante
    return linhas

Defina limites para impedir que um cliente envie dados sem delimitador e consuma memória indefinidamente.

Escrita parcial e backpressure

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

send() pode enviar menos bytes que o solicitado. Mantenha o restante no buffer. Monitore EVENT_WRITE somente quando houver dados pendentes; sockets quase sempre estão prontos para escrita, e registrá-los continuamente pode criar um loop ocupado.

Modificar eventos registrados

def atualizar_interesse(sock, estado, seletor):
    eventos = selectors.EVENT_READ
    if estado["saida"]:
        eventos |= selectors.EVENT_WRITE
    seletor.modify(sock, eventos, estado)

modify() altera máscara e dados de forma mais eficiente que remover e registrar novamente.

Encerrar uma conexão

def fechar(sock, seletor):
    try:
        seletor.unregister(sock)
    except KeyError:
        pass
    sock.close()

Remova o objeto antes do fechamento. Trate erros de conexão individualmente para que um cliente defeituoso não derrube o servidor inteiro.

Diferenças entre plataformas

No Windows, os seletores suportam sockets, mas pipes comuns não funcionam da mesma forma que em Unix. Em Unix, sockets, pipes e FIFOs podem ser suportados, dependendo da implementação.

DefaultSelector melhora portabilidade, mas não elimina diferenças de objetos monitoráveis. Teste o comportamento nas plataformas realmente suportadas. O módulo não está disponível em WASI.

Dados associados e objetos de sessão

Em projetos maiores, use uma dataclass para manter buffers, endereço, estado do protocolo, prazo e métricas.

from dataclasses import dataclass, field

@dataclass
class Sessao:
    endereco: tuple
    entrada: bytearray = field(default_factory=bytearray)
    saida: bytearray = field(default_factory=bytearray)
    fechando: bool = False

Separar estado e transporte facilita testes. Não armazene dados ilimitados ou segredos desnecessários.

Sinais e interrupções

Desde Python 3.5, o seletor normalmente recalcula o timeout e repete a espera quando um sinal interrompe a chamada e o handler não gera exceção. Ainda assim, o loop deve aceitar uma lista vazia e processar tarefas periódicas.

Uso de CPU

Um loop pode consumir 100% de CPU quando monitora escrita o tempo todo, usa timeout zero sem necessidade ou repete após erros. Registre somente eventos úteis e use um timeout coerente.

Meça quantidade de conexões, bytes em buffers, latência do loop e callbacks lentos. Qualquer processamento demorado bloqueia todas as conexões do mesmo loop.

Trabalho pesado fora do loop

Parsing pequeno pode ocorrer no loop, mas CPU intensiva, consultas bloqueantes e acesso lento a disco devem ser delegados. Ao retornar resultados, acorde o loop por uma técnica segura, como socketpair, fila integrada ou mecanismo do framework.

Exemplo de processamento

def processar(chave, mascara, seletor):
    sock = chave.fileobj
    estado = chave.data
    try:
        if mascara & selectors.EVENT_READ:
            receber(sock, estado)
            for linha in extrair_linhas(estado["entrada"]):
                estado["saida"].extend(linha.upper() + b"\n")

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

        atualizar_interesse(sock, estado, seletor)
    except (ConnectionError, OSError):
        fechar(sock, seletor)

Em produção, inclua limites, logs estruturados, validação do protocolo, cancelamento e encerramento gracioso.

Selectors versus asyncio

asyncio usa conceitos semelhantes e frequentemente utiliza seletores internamente em plataformas compatíveis. Escolha selectors para controle explícito ou integração de baixo nível. Escolha asyncio para tarefas, corrotinas, streams, timeouts e bibliotecas assíncronas.

Erros frequentes

  • Esquecer setblocking(False).
  • Fechar antes de remover do seletor.
  • Esperar que recv() devolva uma mensagem inteira.
  • Ignorar escrita parcial.
  • Monitorar escrita permanentemente.
  • Executar trabalho bloqueante no loop.
  • Assumir suporte igual a pipes no Windows e Unix.

Boas práticas

  • Use DefaultSelector.
  • Mantenha buffers com limites.
  • Registre escrita apenas quando necessária.
  • Trate erros por conexão.
  • Implemente timeouts de protocolo.
  • Libere seletor e sockets com context managers.
  • Teste desconexões, mensagens fragmentadas e clientes lentos.

Conteúdos relacionados

Veja contextvars, ExitStack, shlex, faulthandler e platform.

Consulte a documentação oficial do selectors e a documentação de sockets.

Conclusão

selectors permite construir loops de I/O eficientes e portáveis com controle direto sobre leitura, escrita e estado de cada conexão. A robustez depende de buffers, backpressure, timeouts, tratamento de erros e da ausência de operações bloqueantes no loop.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Fluxo de dados em rede representando contexto assíncrono com contextvars no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    contextvars no Python: contexto assíncrono

    Aprenda contextvars no Python para armazenar estado por tarefa, evitar vazamentos em asyncio, copiar contextos e restaurar valores com tokens.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Código de programação representando operações como funções com operator no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    operator no Python: operações como funções

    Aprenda operator no Python para usar operações como funções, ordenar campos, acessar itens, chamar métodos e trabalhar com pipelines funcionais.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Alfabeto tridimensional representando normalização Unicode com unicodedata no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    unicodedata no Python: normalize Unicode

    Aprenda unicodedata no Python para normalizar Unicode, consultar nomes, categorias, números, caracteres combinantes e largura de exibição.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Rede de servidores representando gerenciamento de recursos com ExitStack no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ExitStack no Python: gerencie recursos

    Aprenda ExitStack no Python para gerenciar arquivos, conexões e limpezas dinâmicas com segurança e ordem previsível.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Pasta com cadeado representando tipos e permissões com stat no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    stat no Python: tipos e permissões

    Aprenda stat no Python para interpretar tipos de arquivo, permissões, links, timestamps, atributos do Windows e flags de Unix com

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Notebook com código representando documentação automática com pydoc no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pydoc no Python: documentação automática

    Aprenda pydoc no Python para gerar ajuda no terminal, HTML e servidor local a partir de docstrings, com segurança ao

    Ler mais

    Tempo de leitura: 7 minutos
    10/08/2026