При запуске нового процесса python multiprocessing
-module использует spawn
-method в Windows (это поведение также можно запустить в Linux, используя mp.set_start_method('spawn')
.
Аргументы командной строки передаются интерпретатору в новом процессе, поэтому связь с родительским процессом может быть установлена, например:
python -c "from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=11)" --multiprocessing-fork
Проблема со встроенными модулями cython (или с замороженными (т.е. созданными с помощью cx_Freeze, py2exe и аналогичными) модулями в целом), что передача им аргументов командной строки больше соответствует
python my_script.py <arguments>
то есть командная строка не обрабатывается интерпретатором автоматически, но должна обрабатываться в сценарии.
multiprocessing
предоставляет функцию под названием , который правильно обрабатывает аргументы командной строки и может использоваться, как показано в ответе Бастиана:
if __name__ == '__main__':
# needed for Cython, as it doesn't set `frozen`-attribute
setattr(sys, 'frozen', True)
# parse command line options and execute it if needed
multiprocessing.freeze_support()
Однако это решение работает только для Windows, как видно из кода:
def freeze_support(self):
'''Check whether this is a fake forked process in a frozen executable.
If so then run code specified by commandline and exit.
'''
if sys.platform == 'win32' and getattr(sys, 'frozen', False):
from .spawn import freeze_support
freeze_support()
Есть отчет об ошибке: multiprocessing freeze_support требуется вне win32, который может / не может быть исправлен в ближайшее время.
Как объяснено в вышеприведенном отчете об ошибке, недостаточно установить атрибут frozen
на True
и вызвать freeze_support
непосредственно из multiprocessing.spawn
, потому что трекер семафоров не обрабатывается правильно.
Я вижу два варианта: либо исправить вашу установку еще не выпущенным патчем из приведенного выше отчета об ошибке, либо использовать подход «сделай сам», представленный ниже.
Вот более ранняя версия этого ответа, которая является более «экспериментальной», но предлагает больше идей / деталей и предлагает решение в некотором стиле Do-It-Yourself.
Я использую linux, поэтому использую mp.set_start_method('spawn')
для имитации поведения окон.
Что происходит в spawn
-режиме? Давайте добавим несколько sleep
, чтобы мы могли исследовать процессы:
#bomb.py
import multiprocessing as mp
import sys
import time
def worker():
time.sleep(50)
print('Worker')
return
if __name__ == '__main__':
print("Starting...")
time.sleep(20)
mp.set_start_method('spawn') ## use spawn!
jobs = []
for i in range(5):
p = mp.Process(target=worker)
jobs.append(p)
p.start()
Используя pgrep python
, мы видим, что сначала есть только один процесс на Python, затем 7 (!) Разных pid
s. Мы можем увидеть аргументы командной строки через cat /proc/<pid>/cmdline
. 5 из новых процессов имеют командную строку
-c "from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=11)" --multiprocessing-fork
и один:
-c "from multiprocessing.semaphore_tracker import main;main(4)"
Это означает, что родительский процесс запускает 6 новых экземпляров интерпретатора python, и каждый вновь запущенный интерпретатор выполняет код, отправленный от родителя через параметры командной строки, информация передается по каналам. Один из этих 6 экземпляров python - трекер, который наблюдает за всем.
Хорошо, а что будет, если cythonized + встраивается? Так же, как и с обычным питоном, с той лишь разницей, что вместо python запускается bomb
-исполняемый файл. Но в отличие от интерпретатора Python, он не выполняет / не знает аргументов командной строки, поэтому функция main
запускается снова и снова.
Есть простое решение: пусть bomb
-exe запустит интерпретатор python.
...
if __name__ == '__main__':
mp.set_executable(<PATH TO PYTHON>)
....
Теперь bomb
больше не многопроцессорная бомба!
Однако цель, вероятно, не в том, чтобы иметь под рукой интерпретатор python, поэтому нам нужно сделать так, чтобы наша программа знала о возможных командных строках:
import re
......
if __name__ == '__main__':
if len(sys.argv)==3: # should start in semaphore_tracker mode
nr=list(map(int, re.findall(r'\d+',sys.argv[2])))
sys.argv[1]='--multiprocessing-fork' # this canary is needed for multiprocessing module to work
from multiprocessing.semaphore_tracker import main;main(nr[0])
elif len(sys.argv)>3: # should start in slave mode
fd, pipe=map(int, re.findall(r'\d+',sys.argv[2]))
print("I'm a slave!, fd=%d, pipe=%d"%(fd,pipe))
sys.argv[1]='--multiprocessing-fork' # this canary is needed for multiprocessing module to work
from multiprocessing.spawn import spawn_main;
spawn_main(tracker_fd=fd, pipe_handle=pipe)
else: #main mode
print("Starting...")
mp.set_start_method('spawn')
jobs = []
for i in range(5):
p = mp.Process(target=worker)
jobs.append(p)
p.start()
Теперь наша бомба не нуждается в автономном интерпретаторе python и останавливается после того, как рабочие закончили работу. Обратите внимание на следующее:
- Способ принятия решения о том, в каком режиме
bomb
следует запускать, не очень безопасен, но я надеюсь, что вы уловили суть
--multiprocessing-fork
- это просто канарейка, она ничего не делает, только должна быть там, см. здесь.
NB: измененный код также можно использовать с python, потому что после выполнения "from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=11)" --multiprocessing-fork
python изменяет sys.argv
, поэтому код больше не видит исходную командную строку и len(sys.argv)
становится 1
.
person
ead
schedule
17.11.2017