У меня возникли проблемы с ProcessPoolExecutor. Следующий код пытается найти кратчайший путь в игре WikiRace, он получает 2 названия и перемещается между ними.

Вот мой код:

class AsyncSearch:
    def __init__(self, start, end):
        self.start = start
        self.end = end
        # self.manager = multiprocessing.Manager()
        self.q = multiprocessing.Queue()
        # self.q = self.manager.Queue()

    def _add_starting_node_page_to_queue(self):
        start_page = WikiGateway().page(self.start)
        return self._check_page(start_page)

    def _is_direct_path_to_end(self, page):
        return (page.title == self.end) or (page.links.get(self.end) is not None)

    def _add_tasks_to_queue(self, pages):
        for page in pages:
            self.q.put(page)

    def _check_page(self, page):
        global PATH_WAS_FOUND_FLAG
        logger.info('Checking page "{}"'.format(page.title))
        if self._is_direct_path_to_end(page):
            logger.info('##########\n\tFound a path!!!\n##########')
            PATH_WAS_FOUND_FLAG = True
            return True
        else:
            links = page.links
            logger.info("Couldn't find a direct path form \"{}\", "
                        "adding {} pages to the queue.".format(page.title, len(links)))
            self._add_tasks_to_queue(links.values())
            return "Couldn't find a direct path form " + page.title

    def start_search(self):
        global PATH_WAS_FOUND_FLAG
        threads = []
        logger.debug(f'Running with concurrent processes!')
        if self._add_starting_node_page_to_queue() is True:
            return True
        with concurrent.futures.ProcessPoolExecutor(max_workers=AsyncConsts.PROCESSES) as executor:
            threads.append(executor.submit(self._check_page, self.q.get()))

Я получаю следующее исключение:

Traceback (most recent call last):
  File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\queues.py", line 241, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
  File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\queues.py", line 58, in __getstate__
    context.assert_spawning(self)
  File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\context.py", line 356, in assert_spawning
    ' through inheritance' % type(obj).__name__
RuntimeError: Queue objects should only be shared between processes through inheritance

Это странно, так как я использую multiprocessing.Queue(), который должен быть разделен между процессами, как указано в исключении.

Я нашел этот похожий вопрос, но не нашел там ответа.

Я пытался использовать self.q = multiprocessing.Manager().Queue() вместо self.q = multiprocessing.Queue(), я не уверен, приведет ли это меня куда-нибудь, но исключение, которое я получаю, отличается:

Traceback (most recent call last):
  File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\queues.py", line 241, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
  File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\process.py", line 282, in __reduce__
    'Pickling an AuthenticationString object is '
TypeError: Pickling an AuthenticationString object is disallowed for security reasons

Кроме того, когда я пытаюсь использовать multiprocessing.Process() вместо ProcessPoolExecutor, я не могу завершить процесс, как только нахожу путь. Я установил глобальную переменную, чтобы остановить PATH_WAS_FOUND_FLAG, чтобы остановить запуск процесса, но все равно безуспешно. Что мне здесь не хватает?

1
Tomer Smadja 31 Мар 2020 в 23:52

1 ответ

ProcessPoolExecutor.submit(...) не обрабатывает экземпляры класса multiprocessing.Queue, а также другие экземпляры общего класса multiprocessing.*. Вы можете сделать две вещи: во-первых, использовать SyncManager, или вы можете инициализировать рабочий процесс с экземпляром multiprocessing.Queue во время создания ProcessPoolExecutor. Оба показаны ниже.

Ниже приведен ваш первоначальный вариант с несколькими примененными исправлениями (см. примечание в конце)... в этом варианте многопроцессорность. Операции с очередью выполняются немного быстрее, чем в приведенном ниже варианте SyncManager...

global_page_queue = multiprocessing.Queue()
def set_global_queue(q):
    global global_page_queue
    global_page_queue = q

class AsyncSearch:
    def __init__(self, start, end):
        self.start = start
        self.end = end
        #self.q = multiprocessing.Queue()
    ...
    def _add_tasks_to_queue(self, pages):
        for page in pages:
            #self.q.put(page)
            global_page_queue.put(page)

    @staticmethod
    def _check_page(self, page):
        ...

    def start_search(self):
        ...
        print(f'Running with concurrent processes!')
        with concurrent.futures.ProcessPoolExecutor(
                max_workers=5, 
                initializer=set_global_queue, 
                initargs=(global_page_queue,)) as executor:
            f = executor.submit(AsyncSearch._check_page, self, global_page_queue.get())
            r = f.result()
            print(f"result={r}")

Ниже приведен вариант SyncManager, в котором операции с очередью выполняются немного медленнее, чем описанная выше многопроцессорность. Вариант очереди...

import multiprocessing
import concurrent.futures

class AsyncSearch:
    def __init__(self, start, end):
        self.start = start
        self.end = end
        self.q = multiprocessing.Manager().Queue()
    ...
    @staticmethod
    def _check_page(self, page):
        ...

    def start_search(self):
        global PATH_WAS_FOUND_FLAG
        worker_process_futures = []
        print(f'Running with concurrent processes!')
        with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
            worker_process_futures.append(executor.submit(AsyncSearch._check_page, self, self.q.get()))
            r = worker_process_futures[0].result()
            print(f"result={r}")

Обратите внимание, что для некоторых общих объектов SyncManager может быть немного медленнее или заметно медленнее по сравнению с многопроцессорной обработкой*. Например, multiprocessing.Value находится в общей памяти, а SyncManager.Value — в процессах диспетчера синхронизации, поэтому для взаимодействия с ним требуются дополнительные ресурсы.

Кроме того, не связанного с вашим запросом, ваш исходный код передавал _check_page с неверными параметрами, где вы передавали удаленный из очереди элемент себе, оставляя параметр страницы None. Я решил это, изменив _check_page на статический метод и передав self.

1
Ashley 11 Апр 2022 в 05:58