В некоторых статьях мне рассказывалось, что при создании нового подпроцесса операционная система почти скопирует все данные из partent, включая структуру процессов, стек, кучу и так далее. Итак, я думаю, что глобальные переменные, статические переменные могут быть скопированы в подпроцесс, чьи contens равны родительским значениям при моменте вызова fork(). Но после результата кода python меня путают:
from multiprocessing import Process
ids = []
ids.extend([1, 2, 3, 4])
def worker(sub_id):
global ids
print("sub_id=%s, the content of ids: [%s]" % (sub_id, ",".join(["%s" % x for x in ids])))
def init():
global ids
ids.append(-100)
def main():
init()
sub_process = list()
for i in range(2):
process = Process(target=worker, args=(i, ))
process.start()
sub_process.append(process)
for p in sub_process:
p.join()
global ids
ids.append(100)
print("the main process, the content of ids: [%s]" % (",".join(["%s" % x for x in ids])))
if __name__ == "__main__":
main()
результаты выполнения вышеуказанных кодов:
sub_id=0, the content of ids: [1,2,3,4]
sub_id=1, the content of ids: [1,2,3,4]
the main process, the content of ids: [1,2,3,4,-100,100]
Ожидаемые результаты:
sub_id=0, the content of ids: [1,2,3,4, -100]
sub_id=1, the content of ids: [1,2,3,4, -100]
the main process, the content of ids: [1,2,3,4,-100,100]
Я не знаю, почему изменения ids
в функции init()
не копируются в ids.extend([1, 2, 3, 4])
, но изменения в глобальном разделе ids.extend([1, 2, 3, 4])
видны для ids.extend([1, 2, 3, 4])
.
спасибо за каждый ответ.
Как объясняется в документах, multiprocessing
имеет три разных способа запуска процессов. Двумя основными являются fork
и spawn
. 1
fork
копирует ваш родительский процесс. Вы были правы на носу о том, что это делает: 2 дети начинают с копий родительских глобалов и т.д.
spawn
создает совершенно новый процесс, запускает интерпретатор Python и import
ваш модуль.
В Unix fork
является значением по умолчанию, но в качестве параметров доступны spawn
и forkserver
. В Windows, spawn
является значением по умолчанию и единственным вариантом, поскольку Windows не предоставляет API fork
.
Поскольку вы работаете в Windows, дети не получают копию ids
от родителя, но код ids = []
и ids.extend(…)
запускается при import
, поэтому все они имеют одинаковые значения. Но любой код, защищенный __main__
guard, не запускается при import
, поэтому они не вызывают main
, поэтому нет init
, поэтому no ids.append(-100)
.
multiprocessing
библиотека разработана таким образом, что вы можете использовать ее таким образом, чтобы она работала одинаково на всех платформах. 3 Большинство деталей подробно описаны в разделе "Руководства по программированию " в документах, но основная идея заключается в следующем: не предполагайте, что глобальные скопированные копии скопированы или они не скопированы.
Это означает, что у вас вообще не будет никакого кода вне защитника __main__
на верхнем уровне, за исключением операторов import
, def
и class
и, возможно, нескольких простых глобальных постоянных присвоений.
Любая сложная настройка, которую вам нужно сделать, вы делаете в каждом дочернем процессе. 4 Все, что вы хотите разделить между процессами (например, Lock
или Queue
), вы создаете внутри __main__
guard и передаете детям в качестве аргументов.
1. Для forkserver
см. Документацию для подробностей;это в основном там, где есть программы, которые используют библиотеки, которые ожидают делать причудливые вещи с потоками, которые не очень хорошо работают с fork
, что является обычным явлением в macOS, хотя это может быть полезно и в некоторых других случаях.
2. Есть некоторые сложности в отношении таких вещей, как открытые файлы, но они здесь не важны.
3. Вы можете просто указать spawn
а затем обработать каждую платформу, такую как Windows.Но на некоторых платформах Unix возможно медленное spawn
процессов.Кроме того, получение таких вещей, как совлокальные файлы для работы, может быть болезненным в Unix, поэтому иногда проще писать код, который работает с fork
или spawn
, чем писать код, который работает с spawn
на Unix или spawn
в Windows.Однако использование forkserver
в Unix и spawn
на Windows может, однако, быть хорошим компромиссом /
4. Если вы используете Pool
s, используйте функцию initializer
.
fork
, поэтому по умолчанию используется метод запуска"spawn"
. Для этого нужно запустить совершенно новый интерпретатор Python и импортировать ваш модуль - это означает, что код верхнего уровня, подобный этому,ids = []
иids.extend(…)
запускается в дочернем процессе (но код, защищенный этим__main__
Guard, не Get Run), так что вы получите равный список, но на самом деле он не скопирован из родительского, а просто создан таким же образом.