Технически, с целью обеспечить переносимость этот модуль на разных платформах использует разные инструменты:
• В Unix он использует прием ветвления процессов и вызывает метод run объекта Process в новом дочернем процессе.
• В Windows он запускает новый процесс интерпретатора, используя инструменты Windows создания процессов, передает сериализованный объект Process новому процессу через канал и выполняет команду «python —c» в новом процессе, которая запускает специальную функцию на языке Python в этом пакете. Эта функция читает сериализованную версию объекта Process, распаковывает ее и вызывает метод run.
Мы уже встречались с сериализацией в главе 1 и будем еще изучать ее далее в книге. На самом деле реализация немного сложнее, чем описано выше, и, конечно же, со временем может изменяться, но это действительно очень интересный трюк. Несмотря на то, что переносимый API в целом скрывает подробности реализации от вашего программного кода, тем не менее существуют некоторые тонкие особенности его использования. Например:
• В Windows логику главного процесса вообще следует вкладывать в условную инструкцию проверки условия __name__ == __main__, как это сделано здесь, чтобы модуль можно было импортировать без побочных эффектов. Как мы узнаем в главе 17, при десериализации классов и функций необходимо импортировать вмещающие их модули, что является основным требованием.
• Кроме того, значения глобальных переменных в дочерних процессах на платформе Windows могут отличаться от значений этих же переменных в родительском процессе, имевших место на момент вызова метода start, потому что вмещающие их модули будут импортироваться в новом процессе.
• Дополнительно, в Windows все аргументы конструктора Process должны быть сериализуемыми объектами. Поскольку к числу этих аргументов относится и аргумент target, в нем допускается передавать только простые функции, которые могут быть сериализованы, — в этом аргументе нельзя передавать связанные или несвязанные методы объектов и функции, созданные с помощью инструкции lambda. Подробнее о правилах сериализации рассказывается в описании модуля pickle в руководстве по библиотеке Python — сериализовать можно практически любой объект, но чтобы сериализовать вызываемые объекты, такие как функции и классы, они должны быть доступными для импортирования — эти объекты сериализуются по имени и позднее импортируются для воссоздания байт-кода. В Windows объекты, хранящие системную информацию, такие как подключенные сокеты, вообще не могут использоваться в виде аргументов конструктора Process, потому что они не могут быть сериализованы.
• Точно так же сериализуемыми объектами должны быть экземпляры подклассов класса Process в Windows. Это относится и к значениям их атрибутов. Объекты, доступные в этом пакете (например, Lock в примере 5.29), поддерживают возможность сериализации и поэтому могут использоваться в виде аргументов конструктора Process и в виде атрибутов подклассов.
• Объекты IPC в этом пакете, с которыми мы встретимся в последующих примерах, такие как Pipe и Queue, принимают только сериализуемые объекты, из-за особенностей их реализации (подробнее об этом рассказывается в следующем разделе).
• В Unix дочерний процесс может использовать глобальные элементы, созданные родительским процессом, однако лучше передавать такие объекты дочернему процессу в виде аргументов конструктора Process, что обеспечит совместимость с Windows и позволит избежать возможных проблем на тот случай, если эти объекты будут утилизированы сборщиком мусора в родительском процессе.
В руководстве по библиотеке приводятся и другие правила. Однако в целом, если вы будете придерживаться правил передачи процессам совместно используемых объектов и использовать инструменты взаимодействий, предоставляемые этим пакетом, ваш программный код будет переносим. Теперь рассмотрим практическое применение некоторых из этих инструментов.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011