How to stop Linux threads cleanly
Остановка потоков Linux требует аккуратного подхода, особенно при работе с низкоуровневыми потоками, созданными через pthread_create или std::thread. В отличие от запуска, корректное завершение потоков позволяет выполнить очистку ресурсов — освобождение памяти, блокировок, сброс логов. Идеального универсального решения не существует, но есть несколько подходов.
Простой метод — организация квази-активного ожидания в каждом потоке с проверкой флага остановки. Когда нужно завершить поток, флаг устанавливается в true, после чего вызывается pthread_join. Цикл не должен быть полностью неблокирующим, но должен завершаться за разумное время. Для потоков, блокирующихся на системных вызовах, используются сигналы для прерывания выполнения. Важно, что даже с этим подходом нужно учитывать обработку сигналов для корректной работы с буферизированным выводом.
Комментарии (77)
- Проблема заключается в том, что POSIX не предоставляет безопасного механизма для остановки потока, и это приводит к тому, что разработчики вынуждены полагаться на
pthread_cancel, который небезопасен и может привести к утечкам ресурсов или повреждению состояния. - Попытки использовать
SIGUSR1иsignalfdдля обработки сигналов в пространстве пользователя не решают проблему, потому что большинство библиотек не ожидают, что их вызовы будут прерваны, и это может привести к повреждению состояния. - Вместо этого, вместо попыток остановить поток, который может быть в любой точке, лучше структурировать код так, чтобы он мог реагировать на сигнал остановки, или использовать модель, где потоки не блокируются на системных вызовах, а вместо этого используют асинхронные вызовы и ожидают на
poll/select/epoll/io_uring. - Некоторые комментаторы отмечают, что в Linux существует
io_uring, который позволяет прервать системный вызов, и что это может быть использовано для реализации остановки потока, но это не решает проблему, что не все вызовы могут быть прерваны таким образом. - В конечном счёте, вместо попыток убить поток, который может быть в любой точке, лучше писать код так, чтобы он был отзывчив к сигналу остановки, и использовать модели, где потоки не блокируются на системных вызовах, а вместо этого используют асинхронные вызовы и ожидают на
poll/select/epoll/io_uring.
Without the futex, it's futile 🔥 Горячее
Без futex всё тленно
Книга The Art of Multiprocessor Programming (2-е изд., 2021) считается канонической, но она обходит стороной futex — ключевой примитив современной многопоточности. Это упущение делает пособие бесполезным для практиков.
Futex ≠ mutex
Название происходит от «fast userspace mutex», но futex — это не мьютекс, а основа для всех высокоэффективных примитивов синхронизации. До него всё строилось на громоздких System V IPC, которые не масштабировались: при 1000 потоков futex в 2002-м оказался в 20–120 раз быстрее sysv-блокировок. Windows и macOS добавили аналоги только в 2012 и 2016 гг.
Суть futex
Он разделяет блокировку и ожидание/пробуждение:
- В user-space проверяем состояние; если свободно — захватываем без системного вызова.
- При конфликте вызываем
futex_wait(addr, expected), засыпаем в ядре на конкретном адресе. - Пробуждение —
futex_wake(addr, n), гдеnобычно 1 или «все».
Передаваемое в wait значение защищает от «просыпания» после уже случившегося события: если память изменилась, системный вызов мгновенно возвращает ошибку.
Такой подход убирает лишние системные вызовы и позволяет строить быстрые мьютексы, rw-lock’и, семафоры и пр. без поллинга и экспоненциальных бэкоффов.
Итог
Любая современная библиотека (pthreads, C++ std::mutex, Rust std::sync) опирается на futex. Учебник, игнорирующий этот примитив, не готовит к реальной разработке.
Комментарии (138)
- Futex — это быстрый примитив синхронизации, который экономит системные вызовы при отсутствии конфликтов и не требует выделения ядром объектов.
- В отличие от Benaphore и старых SysV-механизмов, futex не требует предварительной инициализации в ядре и исчезает, когда нет ожидающих.
- Обсуждение подчёркивает, что современные учебники по многопроцессорному программированию обязаны упоминать futex, но «The Art of Multiprocessor Programming» этому не уделяет внимания.
- Улучшения futex2 (Linux ≥5.16) добавили NUMA-поддержку и аналог Windows WaitForMultipleObjects, а io_uring теперь умеет работать с futex.
- Для надёжности при падении потока существуют robust-locks и списки futex, которые ядро автоматически разблокирует.