Мало кто задумывается, как Python ждёт завершения процессов. Но начиная с Python 3.3 (когда появился
timeout в `Popen.wait()`), стандартная библиотека использовала… busy-loop polling.То есть буквально:
waitpid(WNOHANG) → sleep → check → sleep → check ...
С экспоненциальной паузой, да. Но всё равно это означало:
— постоянные пробуждения CPU
— лишние context switches
— задержку между реальным завершением процесса и моментом, когда Python это замечает
— плохую масштабируемость при большом числе процессов
И так жили около 15 лет.
Выяснилось, что современные POSIX-системы позволяют ждать завершения процесса событийно, без опроса:
🐧 Linux (5.3+)
Появился
pidfd_open(): он возвращает file descriptor, связанный с PID.Этот FD можно передать в
poll() и просто блокироваться, пока процесс не завершится.Ядро само разбудит процесс — никаких циклов, никакого sleep.
🍏 macOS / BSD
Тут есть
kqueue(), который умеет напрямую следить за PID и уведомлять о EXIT.🪟 Windows
Тут всё и так было хорошо — используется
WaitForSingleObject, то есть событийная модель была с самого начала.Что это даёт на практике
Тест: процесс ждёт сам себя 10 секунд.
До (busy-loop):
Voluntary context switches: 258
После (event-driven):
Voluntary context switches: 2
То есть вместо постоянного «проснулся → проверил → уснул» процесс просто спит в ядре, как при
time.sleep().Меньше CPU, меньше энергопотребление, лучше масштабируемость.
Где это теперь используется
Сначала решение появилось в psutil, а затем попало прямо в CPython subprocess.
Это редкий случай, когда оптимизация из сторонней библиотеки уходит в стандартную библиотеку Python.
Ирония в том, что 15 лет назад
psutil.wait() вдохновлялся реализацией subprocess.wait(timeout=...).Теперь всё наоборот.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека питониста
#буст