Рубрики
Без рубрики

Различия многопроцессы Python в Windows и Linux

Многоподобность ведет себя совсем по -разному в Windows и Linux. Узнайте различия, чтобы предотвратить ошибки. Tagged с помощью Python, Multiprocessing, Linux, Windows.

Многопроцестра – отличный пакет, если вы когда -нибудь захотите ускорить свой код, не покидая Python. Когда я начал работать с многопроцестрой, я не знал о различиях между Windows и Linux, которые отложили мне несколько недель времени разработки в отношении относительно большого проекта. Давайте быстро посмотрим, как работает многопроцессорная работа и где Windows и Linux расходятся.

Самый быстрый способ показать, как использовать многопроцессорную работу – выполнить простую функцию, не блокируя основную программу:

import multiprocessing as mp
from time import sleep


def simple_func():
    print('Starting simple func')
    sleep(1)
    print('Finishing simple func')


if __name__ == '__main__':
    p = mp.Process(target=simple_func)
    p.start()
    print('Waiting for simple func to end')
    p.join()

Который выводит следующее:

Waiting for simple func to end
Starting simple func
Finishing simple func

Вывод – это то, чего мы ожидали. Давайте перейдем к сути проблемы, изучив, как ведет себя этот код:

import multiprocessing as mp
from time import sleep


print('Before defining simple_func')

def simple_func():
    print('Starting simple func')
    sleep(1)
    print('Finishing simple func')


if __name__ == '__main__':
    p = mp.Process(target=simple_func)
    p.start()
    print('Waiting for simple func to end')
    p.join()

Если мы запустим этот код в Windows, мы получим следующий вывод:

Before defining simple_func
Waiting for simple func to end
Before defining simple_func
Starting simple func
Finishing simple func

В то время как на Linux мы получаем следующий выход:

Before defining simple_func
Waiting for simple func to end
Starting simple func
Finishing simple func

Это не так много, за исключением второго Перед определением simple_func , и эта разница имеет решающее значение. На Linux , когда вы начинаете дочерний процесс, это Разветвлен . Это означает, что дочерний процесс наследует состояние памяти родительского процесса. В Windows (и по умолчанию на Mac), однако, процессы являются Порождено . Это означает, что новый переводчик начинается, а повторное заведение кода.

Это объясняет, почему, если мы запустим код в Windows, мы получим вдвое больше строки Перед определением simple_func Анкет Как вы могли заметить, это могло бы быть намного хуже, если бы мы не включили Если __main__ В конце файла давайте проверим это. В Windows он создает очень длинную ошибку, которая заканчивается:

RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

Находясь на Linux, это работает просто отлично. Это может показаться не так много, но представьте, что у вас есть какая -то вычислительная задача инициализации. Возможно, вы делаете некоторые проверки системы, когда программа работает. Наверное, вы не хотите запускать все эти чеки для каждого процесса, который вы начинаете. Это может стать еще интереснее, если у вас есть значения, которые изменяются во время выполнения:

import multiprocessing as mp
import random

val = random.random()

def simple_func():
    print(val)


if __name__ == '__main__':
    print('Before multiprocessing: ')
    simple_func()
    print('After multiprocessing:')
    p = mp.Process(target=simple_func)
    p.start()
    p.join()

В Windows это даст такой выход:

Before multiprocessing:
0.16042209710776734
After multiprocessing:
0.9180213870647225

Находясь на Linux, он дает такой выход:

Before multiprocessing:
0.28832424513226507
After multiprocessing:
0.28832424513226507

И это подводит нас к последней теме и причине, по которой я потерял так много времени, когда мне пришлось портировать код, написанный в Linux для работы в Windows. Типичная ситуация, в которой значения меняются во время выполнения, – это когда вы работаете с классами. Объекты предназначены для сохранения значений; Они не статичны. Итак, что произойдет, если вы попытаетесь запустить метод класса в отдельном процессе? Начнем с простой задачи:

import multiprocessing as mp


class MyClass:
    def __init__(self, i):
        self.i = i

    def simple_method(self):
        print('This is a simple method')
        print(f'The stored value is: {self.i}')

    def mp_simple_method(self):
        self.p = mp.Process(target=self.simple_method)
        self.p.start()

    def wait(self):
        self.p.join()


if __name__ == '__main__':
    my_class = MyClass(1)
    my_class.mp_simple_method()
    my_class.wait()

Код работает нормально как на Linux, так и на Windows. И это может произойти для множества различных сценариев, пока однажды вы не попытаетесь сделать что -то немного более сложное, например, написание или чтение из файла:

import multiprocessing as mp


class MyClass:
    def __init__(self, i):
        self.i = i
        self.file = open(f'{i}.txt', 'w')

    def simple_method(self):
        print('This is a simple method')
        print(f'The stored value is: {self.i}')

    def mp_simple_method(self):
        self.p = mp.Process(target=self.simple_method)
        self.p.start()

    def wait(self):
        self.p.join()
        self.file.close()


if __name__ == '__main__':
    my_class = MyClass(1)
    my_class.mp_simple_method()
    my_class.wait()

На Linux приведенный выше код работает нормально. В Windows (и Mac), однако, будет очень неприятная ошибка:

[...]
    ForkingPickler(file, protocol).dump(obj)
TypeError: cannot serialize '_io.TextIOWrapper' object

Обратите внимание на тот факт, что мы ничего не делаем с файлом. Мы просто открываем и храним его как атрибут в классе. Однако ошибка уже указывает на интересную функцию. Способ нереста – это замораживание всего объекта. Поэтому, если у нас есть класс или атрибут, который не является Выбор , мы не сможем запустить с ним дочерний процесс.

И для людей, работающих с аппаратным средством, скорее всего, общение с устройством, почти так же, как файл не поддается. Неважно, сколько вы пытаетесь сделать его многопроцессорной безопасностью, внедряя замки или еще много чего. Корневая проблема находится на более низком уровне.

Есть ли способ решения?

К сожалению, нет способа изменить то, как процессы начинаются в Windows. С другой стороны, вы можете изменить, как процессы начинаются на Linux. Это позволило бы вам быть уверенным, что ваша программа также работает на Windows и Mac. Нам просто нужно добавить следующее:

if __name__ == '__main__':
    mp.set_start_method('spawn')
    my_class = MyClass(1)
    my_class.mp_simple_method()
    my_class.wait()

С помощью set_start_method Программа даст ту же ошибку в Windows и Linux. Независимо от того, нужно ли вам добавить эту линию или нет, зависит от того, чего вы хотите достичь.

Итак, если вы когда-нибудь сталкиваетесь с этими несоответствиями, вам придется переосмыслить дизайн вашей программы. У меня были объекты с не поддающимися атрибутам, особенно драйверы для устройств и гнезда ZMQ.

Скорость – еще один фактор

Несмотря на то, что процессы обычно ускоряют скорость программы, используя несколько ядер на компьютере, запуск каждого процесса может быть трудоемким. Тот факт, что в Windows и Mac Python необходимо соленый огурец Объекты для создания дочерних процессов добавляют накладные расходы, которые могут компенсировать преимущества работы на разделенных процессах. Это особенно актуально, когда у вас есть много небольших задач для выполнения, вместо пары давних.

Следовательно, при использовании процессов улучшение скорости программы не является предоставленным результатом. Вы всегда должны сравнить свое заявление, чтобы понять, где и как различные компоненты могут повлиять на его поведение.

Оригинал: “https://dev.to/aquicarattino/differences-of-python-multiprocessing-on-windows-and-linux-3fff”