Resumo:
- O módulo
subprocessdo Python e a bibliotecapsutilusaram, nos últimos 15 anos, uma abordagem ineficiente de “busy-loop polling” ao esperar o encerramento de processos (wait()), repetindosleepewaitpid. - Esse método causa wake-ups desnecessários da CPU, consumo de bateria, latência na detecção do término de processos e tem baixa escalabilidade ao monitorar muitos processos.
- Com atualizações recentes, no Linux passou a ser implementada uma espera realmente orientada a eventos usando
pidfd_open()epoll(), e em BSD/macOS usandokqueue(). - O Windows já usava
WaitForSingleObject, então não houve mudanças, mas em sistemas POSIX os context switches desnecessários foram eliminados e o uso de CPU tende a “0”.
Resumo detalhado:
1. O problema que durou 15 anos: busy-loop polling
Desde que o parâmetro timeout foi adicionado ao subprocess.Popen.wait() no Python 3.3, a biblioteca padrão do Python e a amplamente usada biblioteca psutil vinham usando uma abordagem ineficiente para esperar o término de processos.
A lógica anterior era simples, mas ineficiente:
- Verificar o estado do processo com
waitpid(WNOHANG)(não bloqueante) - Se ainda não tiver terminado, fazer um
sleep()curto (com exponential backoff) - Voltar ao passo 1 e repetir
# método anterior (código conceitual)
import time, os
def wait_busy(pid, timeout):
delay = 0.0001
while True:
# verifica se o processo terminou (polling)
if os.waitpid(pid, os.WNOHANG) == (pid, status):
return status
time.sleep(delay)
delay = min(delay * 2, 0.040) # aumenta o tempo de espera até 40 ms no máximo
Esse método tem três desvantagens críticas.
- Wake-ups da CPU: por maior que seja o tempo de espera, o sistema ainda precisa acordar periodicamente para verificar o estado, desperdiçando ciclos de CPU e consumindo energia.
- Latência: há um intervalo inevitável entre o momento em que o processo realmente termina e o momento em que isso é detectado após sair do
sleep. - Escalabilidade: em ambientes de servidor que precisam monitorar centenas ou milhares de processos ao mesmo tempo, esse overhead cresce rapidamente.
2. A solução: espera orientada a eventos para sistemas POSIX
Todos os sistemas POSIX oferecem mecanismos (select, poll, epoll, kqueue) para detectar mudanças de estado em descritores de arquivo. Recentemente, Python e psutil foram aprimorados para usar isso também na detecção de PIDs de processos.
- Linux: usa a system call
pidfd_open(), introduzida no kernel Linux 5.3 em 2019. Ela retorna um descritor de arquivo que aponta para o PID do processo, permitindo registrar esse descritor empoll()ouepoll()para monitorar o evento de término do processo. (Adicionada ao móduloosa partir do Python 3.9) - BSD / macOS: usa o filtro
EVFILT_PROCda system callkqueue()para monitorar eventos de processo com eficiência. - Windows: já suportava espera orientada a eventos por meio da API
WaitForSingleObject, então não houve mudanças.
3. Melhorias de desempenho e resultados
Com essa mudança, durante a chamada de wait() o processo fica, do ponto de vista do kernel, em estado de “interruptible sleep”. Ou seja, sem consumir CPU alguma, ele aguarda silenciosamente no espaço do kernel e desperta imediatamente quando o sinal de término do processo ocorre.
Segundo benchmarks feitos com /usr/bin/time -v e ferramentas semelhantes, em comparação com o método anterior houve uma redução drástica nos context switches desnecessários, e a velocidade de detecção do término do processo também melhorou de forma imediata. Essa atualização foi incorporada à biblioteca psutil e ao núcleo do CPython, de modo que, daqui para frente, desenvolvedores Python poderão aproveitar o ganho de desempenho sem precisar alterar seus próprios códigos.
Ainda não há comentários.