Справедливости ради следует отметить, что помимо дополнительных инструментов и возможностей пакет multiprocessing несет с собой также дополнительные ограничения, кроме тех, что мы уже обсудили (поддержка возможности сериализации, доступ к совместно используемым данным и так далее). Например, взгляните на следующий фрагмент программного кода:
def action(arg1, arg2): print(arg1, arg2)
if __name__ == ‘__main__’:
Process(target=action, args=(‘spam’, ‘eggs’)).start() # оболочка ждет
# завершения потомка
Этот сценарий действует, как ожидается, но если изменить последнюю строку, как показано ниже, он не будет работать в Windows, потому что lambda-функции не могут быть сериализованы (в действительности, они не могут быть импортированы):
Process(target=(lambda: action(‘spam’, ‘eggs’))).start() # не работает! — # не сериализуется
Это не позволяет использовать распространенный прием программирования с применением lambda-функций для передачи данных в вызовы, который мы часто будем использовать для передачи функций обратного вызова в части книги, посвященной графическим интерфейсам. Кроме того, данная особенность отличает пакет multiprocessing от модуля threading, который послужил прототипом для этого пакета, — вызовы функций, которые могут использоваться при работе с потоками выполнения, такие как приведены ниже, необходимо преобразовать в вызываемые объекты и аргументы:
threading.Thread(target=(lambda: action(2, 4))).start() # но с потоками
# lambda-функции
# работают
И напротив, некоторые особенности поведения модуля threading имитируются в пакете multiprocessing, хотите вы этого или нет. Из-за того, что программы, использующие этот пакет, по умолчанию ожидают завершения дочерних процессов, мы вынуждены помечать процессы, устанавливая атрибут daemon, если нежелательно, чтобы программный код, как показано ниже, блокировал командную оболочку (технически, родительский процесс пытается завершить дочерние процессы-демоны при выходе, то есть программа может завершиться, только когда все потомки являются демонами, что очень похоже на модуль threading):
def action(arg1, arg2): print(arg1, arg2) time.sleep(5) # обычно препятствует завершению родителя
if __name__ == ‘__main__’:
p = Process(target=action, args=(‘spam’, ‘eggs’)) p.daemon = True # не ждать завершения этого потомка p.start()
Дополнительные подробности о некоторых из этих проблем вы найдете в руководстве по библиотеке Python. Они являются не непреодолимыми препятствиями, но специальными случаями и потенциальными ловушками. Мы еще вернемся к проблемам lambda-выражений и процессов-демонов в более прагматичном контексте в главе 8, где будем использовать модуль multiprocessing для запуска графических интерфейсов, выполняющихся независимо.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011