Ответ
По умолчанию Celery использует процессы (модель prefork), а не потоки. Это основной и наиболее стабильный способ параллельного выполнения задач.
Почему процессы, а не потоки?
- Обход Global Interpreter Lock (GIL) в Python: GIL не позволяет нескольким нативным потокам одновременно выполнять Python-байткод. Использование отдельных процессов позволяет полностью утилизировать все ядра CPU для CPU-bound задач.
- Изоляция: Сбой в одной задаче (например, ошибка сегментации) не повлияет на другие задачи, так как они выполняются в изолированных процессах.
Доступные модели параллелизма (пулы):
Celery позволяет изменять пул выполнения с помощью флага -P (--pool).
prefork(по умолчанию): Пул процессов. Идеально для задач, интенсивно использующих CPU.gevent/eventlet: Используют "зеленые" потоки (greenlets). Отлично подходят для I/O-bound задач (например, сетевые запросы, работа с файлами), так как могут обрабатывать тысячи одновременных задач с минимальными накладными расходами.threads: Пул нативных потоков ОС. Может быть полезен для I/O-bound задач, если использованиеgeventилиeventletпо каким-то причинам невозможно. Считается менее надежным, чемprefork.
Пример запуска worker с пулом gevent:
# Запуск worker с 1000 greenlets для обработки I/O-задач
celery -A my_project worker -P gevent --concurrency=1000 -l info Ответ 18+ 🔞
Ах ты ж ёпта, ну слушай, тут народ часто путается, как эта ваша Celery задачи-то параллелит. Так вот, по дефолту она работает на процессах, на этой самой модели prefork. Не на потоках, а именно на отдельных процессах. Это её основной, проверенный годами и стабильный как швейцарский банк режим.
А нахуя процессы, а не потоки, спросишь?
- Чтоб обойти этот ёбаный Global Interpreter Lock (GIL) в Python! Эта штука не даёт нескольким потокам одновременно байтики питоньи выполнять. А процессы — они как отдельные вселенные: каждый со своим GIL'ом, и могут все ядра процессора загрузить по полной, особенно если задачи у тебя CPU-bound, то есть жрут проц.
- Изоляция, блядь! Одна задача взяла и накрылась медным тазом, сегфолтнулась там — остальным хоть бы хны. Они в своих песочницах сидят, не колышется.
Но если очень хочется, можно и другие пулы подключить!
В Celery есть флаг -P (или --pool), им и переключаешь.
prefork(тот самый, дефолтный): Пул процессов. Для задач, где надо мозги процессора напрячь — самое то.gevent/eventlet: А вот это уже для I/O-bound задач, где ты в основном ждёшь ответа от сети, базы или файловой системы. Тут вместо тяжёлых процессов — лёгкие "зелёные" потоки (greenlets). Тысячи их можно запустить, овердохуища задач параллельно обрабатывать.threads: Ну, пул на обычных потоках ОС. На крайняк, еслиgeventне завелся. Но считается, что с ним можно и наебнуться, не так надёжно, как сprefork.
Вот, смотри, как I/O-задачи гонять на gevent:
# Запускаем воркера с целой толпой в 1000 greenlets, пусть работают, суки!
celery -A my_project worker -P gevent --concurrency=1000 -l info
Вот и вся магия. Выбирай пул под задачу, а не просто тыкай наугад, а то потом удивляешься, почему всё тормозит или память жрёт.