У меня возникли проблемы с 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 ответ
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.
Похожие вопросы
Связанные вопросы
Новые вопросы
python-3.x
НЕ ИСПОЛЬЗУЙТЕ, ЕСЛИ ВАШ ВОПРОС ТОЛЬКО ДЛЯ PYTHON 3. Всегда используйте вместе со стандартным тегом [python].