справочник по Python. мм isbn 9785932861578 9 785932 861578
Скачать 4.21 Mb.
|
m.Queue() Создает на стороне сервера разделяемый экземпляр Queue.Queue и возвраща- ет прокси-объект для доступа к нему. m.RLock() Создает на стороне сервера разделяемый экземпляр threading.Rlock и воз- вращает прокси-объект для доступа к нему. m.Semaphore([value]) Создает на стороне сервера разделяемый экземпляр threading.Semaphore и возвращает прокси-объект для доступа к нему. m.Value(typecode, value) Создает на стороне сервера разделяемый объект типа Value и возвраща- ет прокси-объект для доступа к нему. Описание аргументов можно найти в разделе «Совместно используемые данные и синхронизация». Следующий пример демонстрирует, как с помощью менеджера можно соз- дать словарь для совместного использования несколькими процессами. import multiprocessing import time ёё # выводит содержимое словаря d всякий раз, # когда устанавливается переданное событие def watch(d, evt): while True: evt.wait() print(d) evt.clear() ёё if __name__ == ‘__main__’: m = multiprocessing.Manager() d = m.dict() # Создать разделяемый словарь evt = m.Event() # Создать разделяемое событие ёё # Запустить процесс, который выводит содержимое словаря p = multiprocessing.Process(target=watch,args=(d,evt)) p.daemon=True p.start() ёё # Добавить элемент словаря и известить процесс вывода его содержимого d[‘foo’] = 42 evt.set() time.sleep(5) ёё # Добавить элемент словаря и известить процесс вывода его содержимого d[‘bar’] = 37 538 Глава 20. Потоки и многозадачность evt.set() time.sleep(5) ёё # Завершить процесс вывода и процесс менеджера p.terminate() m.shutdown() Е сли запустить этот пример, функция watch() будет выводить содержи- мое словаря d всякий раз, когда будет генерироваться событие. Главная программа создает разделяемый словарь и событие и манипулирует ими в главном процессе. После запуска примера можно увидеть, как дочерний процесс выводит данные. Если в программе потребуется использовать разделяемые объекты других типов, например экземпляры пользовательских классов, можно создать собственный объект менеджера. Для этого нужно определить класс, произ- водный от класса BaseManager, который объявлен в подмодуле multiprocess- ing.managers managers.BaseManager([address [, authkey]]) Базовый класс BaseManager используется для создания нестандартных клас- сов-менеджеров, управляющих пользовательскими объектами. В необяза- тельном аргументе address передается кортеж (hostname, port), определяю- щий сетевой адрес сервера. Если этот аргумент опустить, операционная си- стема просто присвоит адрес, соответствующий свободному номеру порта. В аргументе authkey передается строка, которая будет использоваться для аутентификации клиентов при подключении к серверу. Если этот аргумент опустить, будет использоваться значение current_process().authkey. Экземпляр mgrclass класса, производного от класса BaseManager, может ис- пользовать следующий метод класса для создания методов возвращаемого прокси-объекта, обеспечивающего доступ к разделяемым объектам. mgrclass.register(typeid [, callable [, proxytype [, exposed [, method_to_typeid [, create_method]]]]]) Регистрирует новый тип данных в классе менеджера. Аргумент typeid – строка, которая используется в качестве имени типа разделяемого объ- екта. Эта строка должна быть допустимым идентификатором. В аргумен- строка должна быть допустимым идентификатором. В аргумен- строка должна быть допустимым идентификатором. В аргумен- должна быть допустимым идентификатором. В аргумен- должна быть допустимым идентификатором. В аргумен- быть допустимым идентификатором. В аргумен- быть допустимым идентификатором. В аргумен- допустимым идентификатором. В аргумен- допустимым идентификатором. В аргумен- идентификатором. В аргумен- идентификатором. В аргумен- . В аргумен- В аргумен- аргумен- аргумен- те callable передается вызываемый объект, создающий и возвращающий экземпляр разделяемого объекта. В аргументе proxytype передается класс, представляющий реализацию прокси-объектов, которые будут использо- ваться клиентами. Обычно эти классы создаются автоматически, поэтому часто в этом аргументе передается значение None. В аргументе exposed пере- дается последовательность имен методов разделяемого объекта, к которым можно будет обращаться с помощью прокси-объекта. Если опустить этот аргумент, будет использоваться значение атрибута proxytype._exposed_, а если и аргумент proxytype не определен, то будет создан список всех обще- доступных методов (все методы, имена которых не начинаются с символа подчеркивания (_)). В аргументе method_to_typeid передается отображение имен методов в идентификаторы типов возвращаемых ими значений, ко- торое будет использоваться, чтобы определить, какие методы должны воз- Модуль multiprocessing 539 вращать результаты с помощью прокси-объекта. Возвращаемые значения методов, отсутствующих в этом отображении, будут просто копироваться и возвращаться. Если в аргументе method_to_typeid передать None, будет ис- пользовано значение атрибута proxytype._method_to_typeid_, если этот ар- гумент определен. В аргументе create_method передается логический флаг, определяющий, должен ли создаваться метод с именем typeid в экземпляре mgrclass . По умолчанию принимает значение True. Чтобы задействовать экземпляр m класса менеджера, производного от класса BaseManager, его необходимо запустить вручную. Ниже перечислены атрибуты и методы, имеющие прямое отношение к этому: m.address Кортеж (hostname, port) с адресом, который будет использоваться сервером менеджера. m.connect() Устанавливает соединение с удаленным объектом менеджера, адрес кото- рого был указан при вызове конструктора класса BaseManager. m.serve_forever() Запускает сервер менеджера в текущем процессе. m.shutdown() Останавливает сервер менеджера, запущенный методом m.start(). m.start() Запускает дочерний процесс и затем запускает сервер менеджера в этом процессе. Следующий пример демонстрирует, как создаются менеджеры, управляю- щие объектами пользовательских классов: import multiprocessing from multiprocessing.managers import BaseManager ёё class A(object): def __init__(self,value): self.x = value def __repr__(self): return “A(%s)” % self.x def getX(self): return self.x def setX(self,value): self.x = value def __iadd__(self,value): self.x += value return self ёё class MyManager(BaseManager): pass MyManager.register(“A”,A) ёё if __name__ == ‘__main__’: m = MyManager() m.start() 540 Глава 20. Потоки и многозадачность # Создать объект менеджера a = m.A(37) ... Последняя инструкция в этом примере создает экземпляр класса A в про- цессе сервера менеджера. Переменная a в предыдущем примере – это всего лишь прокси-объект для данного экземпляра. Своим поведением прокси- объект напоминает (близко, но не полностью) объект, на который он ссы- лается. Прежде всего, без труда можно обнаружить, что не все атрибуты и свойства разделяемого объекта доступны непосредственно. Вместо пря- мого обращения к атрибуту следует использовать метод доступа: >>> a.x Traceback (most recent call last): File “ AttributeError: ‘AutoProxy[A]’ object has no attribute ‘x’ (Перевод: Трассировочная информация (самый последний вызов – самый нижний): Файл “ AttributeError: Объект ‘AutoProxy[A]’ не имеет атрибута ‘x’ ) >>> a.getX() 37 >>> a.setX(42) >>> При передаче прокси-объекта функции repr() она возвращает строковое представление самого прокси-объекта, тогда как функция str() возвраща- ет результат вызова метода __repr__() разделяемого объекта. Например: >>> a >>> print(a) A(37) >>> Специальные методы, а также любые методы, имена которых начинаются с символа подчеркивания (_), недоступны прокси-объектам. Например, по- пытка вызвать метод a.__iadd__() не увенчается успехом: >>> a += 37 Traceback (most recent call last): File “ TypeError: unsupported operand type(s) for +=: ‘AutoProxy[A]’ and ‘int’ (Перевод: Трассировочная информация (самый последний вызов – самый нижний): Файл “ TypeError: неподдерживаемые типы операндов для +=: ‘AutoProxy[A]’ и ‘int’ ) >>> a.__iadd__(37) Traceback (most recent call last): File “ AttributeError: ‘AutoProxy[A]’ object has no attribute ‘__iadd__’ (Перевод: Трассировочная информация (самый последний вызов – самый нижний): Модуль multiprocessing 541 Файл “ AttributeError: Объект ‘AutoProxy[A]’ не имеет атрибута ‘__iadd__’ ) >>> В более сложных ситуациях можно создать собственный прокси-объект, реализовав в нем более полное управление доступом. Для этого следует определить класс, производный от класса BaseProxy, объявленного в моду- ле multiprocessing.managers. В следующем фрагменте демонстрируется, как можно создать собственный прокси-объект для класса A из предыдущего примера, который обеспечивает доступ к методу __iadd__() и определяет свойство для доступа к атрибуту x: from multiprocessing.managers import BaseProxy ёё class AProxy(BaseProxy): # Список всех методов разделяемого объекта, доступных через прокси-объект _exposed_ = [‘__iadd__’,’getX’,’setX’] # Реализация общедоступного интерфейса прокси-объекта def __iadd__(self,value): self._callmethod(‘__iadd__’,(value,)) return self @property def x(self): return self._callmethod(‘getX’,()) @x.setter def x(self,value): self._callmethod(‘setX’,(value,)) ёё class MyManager(BaseManager): pass MyManager.register(“A”, A, proxytype=AProxy) Экземпляр proxy класса, производного от класса BaseProxy, обладает следу- ющими методами: proxy._callmethod(name [, args [, kwargs]]) Вызывает метод name разделяемого объекта, на который ссылается прокси- объект. В аргументе name передается строка с именем метода, в args пере- дается кортеж с позиционными аргументами и в kwargs – словарь с имено- ванными аргументами вызываемого метода. Имя name метода должно быть явно определено в прокси-объекте. Обычно это делается посредством вклю- чения имени в атрибут класса _exposed_ прокси-объекта. proxy._getvalue() Возвращает вызывающему программному коду копию разделяемого объ- екта, на который ссылается прокси-объект. Если вызов производится из другого процесса, копируемый объект сериализуется, передается вызы- вающему процессу и затем восстанавливается. Если разделяемый объект не поддерживает возможность сериализации, возбуждается исключение. Соединения Программы, использующие модуль multiprocessing, имеют возможность об- мениваться сообщениями с другими процессами, выполняющимися как на 542 Глава 20. Потоки и многозадачность локальном, так и на удаленном компьютере. Это может быть удобно, когда потребуется взять за основу некоторую программу, предназначенную для работы на единственном компьютере, и добавить в нее возможность выпол- няться на кластере. В подмодуле multiprocessing.connection имеются функ- ции и классы, способные помочь в решении этой задачи: connections.Client(address [, family [, authenticate [, authkey]]]) Устанавливает соединение с другим процессом, который уже должен ожи- дать поступление запросов на соединение по адресу address. В аргументе address может передаваться кортеж (hostname, port), представляющий се- тевой адрес, строка с именем файла в формате сокетов UNIX или строка вида r’\\servername\pipe\pipename’, представляющая именованный канал Windows в удаленной системе servername (для обозначения локального ком- пьютера в поле servername следует указывать ‘.’). В аргументе family передается строка, обозначающая формат адреса address , которая обычно принимает одно из значений ‘AF_INET’, ‘AF_UNIX’ или ‘AF_PIPE’ . В случае его отсутствия значение этого аргумента по умолчанию определяется, исходя из формата аргумента address. В аргументе authen- tication передается логический флаг, определяющий, должна ли выпол- няться аутентификация. В аргументе authkey передается строка с ключом аутентификации. Если опустить этот аргумент, в качестве ключа будет ис- . Если опустить этот аргумент, в качестве ключа будет ис- Если опустить этот аргумент, в качестве ключа будет ис- пользоваться значение current_process().authkey. Возвращаемым значени- ем этой функции является объект класса Connection, который был описан выше, в разделе «Взаимодействия между процессами». connections.Listener([address [, family [, backlog [, authenticate [, authkey]]]]]) Возвращает экземпляр класса Listener, реализующего сервер, принимаю- щий и обрабатывающий запросы на соединение, выполняемые функци- ей Client(). Аргументы address, family, authenticate и authkey имеют тот же смысл, что и в функции Client(). В аргументе backlog передается целое чис- ло, соответствующее значению, которое передается методу listen() сокета, если аргумент address соответствует сетевому соединению. По умолчанию аргумент backlog принимает значение 1. Если аргумент address не указан, то используется адрес по умолчанию. Если отсутствуют оба аргумента, ad- dress и family, то выбирается самая быстрая схема взаимодействий в преде- лах локальной системы. Экземпляр s класса Listener поддерживает следующие методы и атрибуты: s.accept() Принимает запрос на соединение и возвращает объект класса Connection. В случае ошибки аутентификации возбуждает исключение Authentication- Error s.address Адрес, используемый принимающей стороной. s.close() Закрывает канал или сокет, используемый принимающей стороной. Модуль multiprocessing 543 s.last_accepted А дрес последнего клиента, с которым было установлено соединение. Ниже приводится пример программы-сервера, которая ожидает появле- ния запросов на соединение от клиентов и реализует простейшую удален- ную операцию (сложение): from multiprocessing.connection import Listener ёё serv = Listener((‘’,15000),authkey=’12345’) while True: conn = serv.accept() while True: try: x,y = conn.recv() except EOFError: break result = x + y conn.send(result) conn.close() Ниже приводится пример простой программы-клиента, которая соединя- ется с сервером и отправляет ему несколько сообщений: from multiprocessing.connection import Client conn = Client((‘localhost’,15000), authkey=”12345”) ёё conn.send((3,4)) r = conn.recv() print(r) # Выведет ‘7’ ёё conn.send((“Hello”,”World”)) r = conn.recv() print(r) # Выведет ‘HelloWorld’ conn.close() Различные вспомогательные функции В модуле также определены следующие вспомогательные функции: active_children() Возвращает список объектов класса Process, соответствующих всем дочер- ним процессам. cpu_count() Возвращает количество процессоров в системе, если их число можно опре- делить. current_process() Возвращает объект класса Process, соответствующий текущему процессу. freeze_support() Вызов этой функции должен включаться в главную программу в виде пер- вой инструкции, если приложение должно поддерживать компиляцию 544 Глава 20. Потоки и многозадачность в двоичные файлы с помощью различных инструментов создания пакетов, таких как py2exe. Это необходимо для предотвращения появления ошибок времени выполнения, связанных с запуском дочерних процессов из при- ложений, скомпилированных в двоичный формат. get_logger() Возвращает объект регистратора для модуля multiprocessing; создает его, если он еще не был создан. Возвращаемый объект регистратора не передает журналируемые сообщения корневому регистратору, имеет уровень log- ging.NOTSET и выводит все сообщения в поток стандартного вывода сообще- ний об ошибках. set_executable(executable) Устанавливает путь к выполняемому файлу интерпретатора Python, кото- Python, кото- , кото- рый будет использоваться для запуска дочерних процессов. Эта функция определена только в версии модуля для Windows. Общие советы по использованию модуля multiprocessing Модуль multiprocessing является одним из самых сложных и мощных моду- лей в стандартной библиотеке языка Python. Ниже приводятся несколько общих советов, которые помогут вам избежать головной боли при работе с ним: • Внимательно прочитайте электронную документацию, прежде чем при- ступать к созданию крупного приложения. Хотя в этом разделе были рассмотрены самые важные основы, тем не менее в официальной доку- ментации описывается множество более коварных проблем, с которы- ми можно столкнуться. • Обязательно убедитесь, что все типы данных, которые участвуют в об- мене между процессами, совместимы с модулем pickle. • Старайтесь не использовать механизмы совместного использования данных и учитесь пользоваться механизмами передачи сообщений и очередями. При использовании механизмов обмена сообщениями вам не придется беспокоиться о синхронизации, блокировках и других про- блемах. Кроме того, такой подход обладает лучшей масштабируемостью при увеличении числа процессов. • Не пользуйтесь глобальными переменными в функциях, которые пред- назначены для работы в отдельных процессах. Вместо этого лучше ис- пользовать явную передачу параметров. • Старайтесь не смешивать поддержку механизма потоков и процессов в одной программе, если только вы не стремитесь существенно повы- сить защищенность своих разработок от постороннего взгляда (или по- низить – в зависимости от того, кто будет просматривать ваш программ- ный код). • Особое внимание обращайте на то, как завершаются процессы. Как правило, предпочтительнее явно закрывать процессы и предусматри- |