Использование нескольких ядер с Python и Eventlet

У меня есть веб-приложение Python, в котором клиент (Ember.js) взаимодействует с сервером через WebSocket (я использую Flask-SocketIO). Помимо сервера WebSocket, бэкэнд делает еще две вещи, о которых стоит упомянуть:

  • Преобразование изображений (используя graphicsmagick)
  • Распознавание входящих изображений от клиента (с использованием tesseract)

Когда клиент отправляет изображение, его сущность создается в базе данных, а идентификатор помещается в очередь преобразования изображения. Рабочий берет его и выполняет преобразование изображения. После этого обработчик помещает его в очередь OCR, где он будет обрабатываться обработчиком очереди OCR.

Все идет нормально. Запросы WS обрабатываются синхронно в отдельных потоках (Flask-SocketIO использует для этого Eventlet), а тяжелые вычислительные действия выполняются асинхронно (также в отдельных потоках).

Теперь проблема: все приложение работает на Raspberry Pi 3. Если я не использую 4 ядра, у меня есть только одно ядро ​​ARMv8 с тактовой частотой 1,2 ГГц. Это очень маленькая мощность для OCR. Поэтому я решил узнать, как использовать несколько ядер с Python. Хотя я читал о проблемах с GIL) я узнал о многопроцессорности где написано The multiprocessing package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads.. Именно то, что я хотел. Поэтому я сразу заменил

from threading import Thread
thread = Thread(target=heavy_computational_worker_thread)
thread.start()

by

from multiprocessing import Process
process = Process(target=heavy_computational_worker_thread)
process.start()

Очередь также должна была обрабатываться несколькими ядрами, поэтому мне пришлось изменить

from queue import Queue
queue = multiprocessing.Queue()

to

import multiprocessing
queue = multiprocessing.Queue()

также. Проблема: очередь и библиотеки потоков обезьяны исправлены с помощью Eventlet. Если я перестану использовать исправленную обезьяной версию Thread и Queue и вместо этого буду использовать версию из multiprocsssing, то поток запроса, запущенный Eventlet, навсегда заблокируется при доступе к очереди.

Теперь мой вопрос:

Можно ли как-нибудь заставить это приложение выполнять распознавание символов и преобразование изображений на отдельном ядре?

Я хотел бы продолжать использовать WebSocket и Eventlet, если это возможно. Преимущество, которое у меня есть, заключается в том, что единственным интерфейсом связи между процессами будет очередь.

Идеи, которые у меня уже были: - Не использовать реализацию очереди на Python, а использовать ввод-вывод. Например, выделенный Redis, к которому будут обращаться различные подпроцессы. Идем дальше: запускаем каждый обработчик очереди как отдельный процесс Python (например, python3 wsserver | python3 ocrqueue | python3 imgconvqueue). Тогда я должен был бы сам убедиться, что доступ к очереди и к базе данных будет неблокирующим.

Однако лучше всего было бы сохранить один процесс и заставить его работать с многопроцессорной обработкой.

заранее большое спасибо


person Schnodderbalken    schedule 15.10.2016    source источник
comment
Я не знаю библиотеку Evenlet, поэтому, возможно, мой ответ будет неприменим. Используйте многопоточность в основной программе. Внутри каждого процесса в этом основном приложении используйте подпроцесс для вызова подпрограмм (программа на Python, вызывающая программу на Python, даже если это кажется странным). Используйте библиотеку Eventlet (или любую небезопасную библиотеку) только в этих подпроцессах. Не используйте их в основной программе. Вы не сможете использовать Queue, но сможете передавать данные через файлы (пример: программа A записывает файл изображения, затем завершается, программа B запускается и читает этот файл).   -  person Sci Prog    schedule 16.10.2016


Ответы (1)


Eventlet в настоящее время несовместим с многопроцессорным пакетом. Для этой работы есть открытая проблема: https://github.com/eventlet/eventlet/issues/ 210.

Альтернативой, которая, я думаю, будет хорошо работать в вашем случае, является использование Celery для управления вашей очередью. Celery запустит пул рабочих процессов, которые ждут задач, предоставленных основным процессом через очередь сообщений (поддерживаются RabbitMQ и Redis).

Обработчикам Celery не нужно использовать eventlet, только основной сервер, так что это позволяет им делать все, что им нужно, без ограничений, налагаемых eventlet.

Если вам интересно изучить этот подход, у меня есть полный пример его использования: https://github.com/miguelgrinberg/flack.

person Miguel    schedule 16.10.2016
comment
Спасибо, это работает как шарм. Единственная проблема, с которой я столкнулся сейчас, заключается в том, что у меня был экземпляр socketio в глобальной переменной, которая, конечно, не может быть разделена между несколькими процессами. Но это должно быть решено с помощью выделенного хранилища, чтобы поделиться им или передать переменную функциям задачи. - person Schnodderbalken; 17.10.2016
comment
См. проект, на который я ссылался в своем ответе. Если вам нужно излучать из рабочих процессов, создайте экземпляр socketio в каждом процессе. Разница в том, что у экземпляра socketio в ваших воркерах Celery не будет связанного с ним экземпляра приложения Flask, поэтому они только испускают. Вам не нужно ничего обменивать между процессами, все коммуникации осуществляются через очередь сообщений. - person Miguel; 17.10.2016
comment
Я следовал инструкциям, которые вы задокументировали в github.com/miguelgrinberg/Flask. -SocketIO/blob/master/docs/ - использование конструктора с message_queue='redis://' в качестве параметра решило проблему :) Таким образом, у меня все еще есть только один экземпляр socketio, позволяющий мне испускать из рабочие процессы. - person Schnodderbalken; 17.10.2016