multiprocessing — Параллелизм на основе процессов
Исходный код: Lib/multiprocessing/
Доступность: не поддерживается на Android, iOS и WASI.
Этот модуль не поддерживается на мобильных платформах и платформах WebAssembly.
Введение
multiprocessing — это пакет, поддерживающий создание процессов с помощью API, аналогичного модулю threading. Пакет multiprocessing обеспечивает параллельное выполнение как на локальных, так и на удалённых системах, эффективно обходя глобальную блокировку интерпретатора за счёт использования дочерних процессов вместо потоков. Благодаря этому модуль multiprocessing позволяет программисту в полной мере использовать несколько процессоров на компьютере. Он работает в POSIX и Windows.
Модуль multiprocessing также предоставляет API, аналогов которому нет в модуле threading. Яркий пример — объект Pool, позволяющий удобным образом распараллелить выполнение функции для нескольких входных значений, распределив входные данные между процессами (параллелизм данных). В следующем примере показан распространённый способ определения таких функций в модуле, чтобы дочерние процессы могли успешно импортировать этот модуль. Этот простой пример параллелизма данных с использованием Pool
from multiprocessing import Pool
def f(x):
return x*x
if __name__ == '__main__':
with Pool(5) as p:
print(p.map(f, [1, 2, 3]))
выведет в стандартный вывод
[1, 4, 9]
См. также
concurrent.futures.ProcessPoolExecutor предоставляет интерфейс более высокого уровня для отправки задач фоновому процессу без блокировки выполнения вызывающего процесса. По сравнению с непосредственным использованием интерфейса Pool, API concurrent.futures позволяет проще отделить отправку задач в пул процессов от ожидания результатов.
Класс Process
В multiprocessing процессы создаются путём создания объекта Process и последующего вызова его метода start(). API класса Process аналогичен API класса threading.Thread. Простейший пример многопроцессной программы:
from multiprocessing import Process
def f(name):
print('hello', name)
if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
p.join()
Чтобы показать идентификаторы отдельных задействованных процессов, приведём расширенный пример:
from multiprocessing import Process
import os
def info(title):
print(title)
print('module name:', __name__)
print('parent process:', os.getppid())
print('process id:', os.getpid())
def f(name):
info('function f')
print('hello', name)
if __name__ == '__main__':
info('main line')
p = Process(target=f, args=('bob',))
p.start()
p.join()
Объяснение необходимости части if __name__ == '__main__' см. в разделе Рекомендации по программированию.
Аргументы для Process обычно должны сериализоваться с помощью pickle в дочернем процессе. Если ввести приведённый выше пример непосредственно в REPL, это может привести к возникновению исключения AttributeError в дочернем процессе, который попытается найти функцию f в модуле __main__.
Контексты и методы запуска
В зависимости от платформы multiprocessing поддерживает три способа запуска процесса. Эти методы запуска:
- spawn
-
Родительский процесс запускает новый процесс интерпретатора Python. Дочерний процесс наследует только те ресурсы, которые необходимы для выполнения метода
run()объекта процесса. В частности, ненужные файловые дескрипторы и дескрипторы родительского процесса не наследуются. Запуск процесса этим методом довольно медленный по сравнению с использованием fork или forkserver.Доступен на платформах POSIX и Windows. Используется по умолчанию в Windows и macOS.
- fork
-
Родительский процесс использует
os.fork(), чтобы создать ответвление интерпретатора Python. После запуска дочерний процесс фактически идентичен родительскому. Дочернему процессу наследуются все ресурсы родительского. Обратите внимание, что безопасное создание ответвления многопоточного процесса проблематично.Доступен в системах POSIX. В настоящее время используется по умолчанию в POSIX, кроме macOS.
Примечание
В Python 3.14 метод запуска по умолчанию изменится с fork на другой. В коде, которому необходим fork, следует явно указать этот метод с помощью
get_context()илиset_start_method().Изменено в версии 3.12: Если Python может определить, что в процессе есть несколько потоков, функция
os.fork(), которую этот метод запуска вызывает внутри, выдаст предупреждениеDeprecationWarning. Используйте другой метод запуска. Дополнительные пояснения см. в документацииos.fork().
- forkserver
-
При запуске программы с выбранным методом запуска forkserver создаётся серверный процесс. После этого, когда требуется новый процесс, родительский процесс подключается к серверу и запрашивает создание нового процесса с помощью fork. Серверный процесс fork работает в одном потоке, если только системные библиотеки или предварительно загруженные импорты не создают потоки как побочный эффект, поэтому, как правило, использовать в нём
os.fork()безопасно. Ненужные ресурсы не наследуются.Доступен на платформах POSIX, поддерживающих передачу файловых дескрипторов через каналы Unix, например Linux.
Изменено в версии 3.4: spawn добавлен на всех платформах POSIX, а forkserver — на некоторых платформах POSIX. Дочерние процессы больше не наследуют все наследуемые дескрипторы родительских процессов в Windows.
Изменено в версии 3.8: В macOS метод запуска spawn теперь используется по умолчанию. Метод запуска fork следует считать небезопасным, поскольку он может привести к сбоям дочернего процесса: системные библиотеки macOS могут запускать потоки. См. bpo-33725.
В POSIX при использовании методов запуска spawn или forkserver также запускается процесс отслеживания ресурсов, который отслеживает именованные системные ресурсы без ссылок (например, именованные семафоры или объекты SharedMemory), созданные процессами программы. После завершения всех процессов отслеживатель ресурсов удаляет все оставшиеся отслеживаемые объекты. Обычно таких объектов не должно быть, но если процесс был завершён сигналом, могут остаться некоторые «утёкшие» ресурсы. (Утёкшие семафоры и сегменты разделяемой памяти не будут автоматически удалены до следующей перезагрузки. Это проблематично для обоих типов объектов, поскольку система допускает только ограниченное число именованных семафоров, а сегменты разделяемой памяти занимают место в основной памяти.)
Чтобы выбрать метод запуска, используйте set_start_method() в блоке if __name__ == '__main__' главного модуля. Например:
import multiprocessing as mp
def foo(q):
q.put('hello')
if __name__ == '__main__':
mp.set_start_method('spawn')
q = mp.Queue()
p = mp.Process(target=foo, args=(q,))
p.start()
print(q.get())
p.join()
set_start_method() не следует вызывать в программе более одного раза.
В качестве альтернативы можно использовать get_context(), чтобы получить объект контекста. Объекты контекста имеют тот же API, что и модуль multiprocessing, и позволяют использовать в одной программе несколько методов запуска.
import multiprocessing as mp
def foo(q):
q.put('hello')
if __name__ == '__main__':
ctx = mp.get_context('spawn')
q = ctx.Queue()
p = ctx.Process(target=foo, args=(q,))
p.start()
print(q.get())
p.join()
Обратите внимание, что объекты, связанные с одним контекстом, могут быть несовместимы с процессами, использующими другой контекст. В частности, блокировки, созданные с помощью контекста fork, нельзя передавать процессам, запущенным методами spawn или forkserver.
Библиотеки, использующие multiprocessing или ProcessPoolExecutor, следует проектировать так, чтобы их пользователи могли передавать собственный контекст multiprocessing. Использование библиотекой собственного конкретного контекста может привести к несовместимости с остальной частью приложения пользователя библиотеки. Всегда документируйте, если для вашей библиотеки требуется определённый метод запуска.
Предупреждение
Методы запуска 'spawn' и 'forkserver' обычно нельзя использовать с «замороженными» исполняемыми файлами (то есть двоичными файлами, созданными такими пакетами, как PyInstaller и cx_Freeze) в системах POSIX. Метод запуска 'fork' может работать, если код не использует потоки.
Обмен объектами между процессами
multiprocessing поддерживает два типа каналов связи между процессами:
Очереди
Класс Queue — почти точная копия queue.Queue. Например:
from multiprocessing import Process, Queue
def f(q):
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
p = Process(target=f, args=(q,))
p.start()
print(q.get()) # prints "[42, None, 'hello']"
p.join()
Очереди безопасны для потоков и процессов. Любой объект, помещённый в очередь multiprocessing, будет сериализован.
Каналы
Функция Pipe() возвращает пару объектов соединения, связанных каналом, который по умолчанию является двунаправленным. Например:
from multiprocessing import Process, Pipe
def f(conn):
conn.send([42, None, 'hello'])
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print(parent_conn.recv()) # prints "[42, None, 'hello']"
p.join()
Два объекта соединения, возвращаемые функцией Pipe(), представляют два конца канала. У каждого объекта соединения есть методы send() и recv() (среди прочих). Обратите внимание, что данные в канале могут повредиться, если два процесса (или потока) одновременно попытаются прочитать данные из одного и того же конца канала или записать в него. Разумеется, одновременная работа процессов с разными концами канала не приводит к повреждению данных.
Метод send() сериализует объект, а recv() воссоздаёт объект.
Синхронизация между процессами
multiprocessing содержит аналоги всех примитивов синхронизации из threading. Например, можно использовать блокировку, чтобы гарантировать, что в каждый момент времени в стандартный вывод пишет только один процесс:
from multiprocessing import Process, Lock
def f(l, i):
l.acquire()
try:
print('hello world', i)
finally:
l.release()
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
Без блокировки вывод разных процессов может перемешаться.
Использование пула рабочих процессов
Класс Pool представляет пул рабочих процессов. Он содержит методы, позволяющие несколькими способами передавать задачи рабочим процессам для выполнения.
Например:
from multiprocessing import Pool, TimeoutError
import time
import os
def f(x):
return x*x
if __name__ == '__main__':
# start 4 worker processes
with Pool(processes=4) as pool:
# print "[0, 1, 4,..., 81]"
print(pool.map(f, range(10)))
# print same numbers in arbitrary order
for i in pool.imap_unordered(f, range(10)):
print(i)
# evaluate "f(20)" asynchronously
res = pool.apply_async(f, (20,)) # runs in *only* one process
print(res.get(timeout=1)) # prints "400"
# evaluate "os.getpid()" asynchronously
res = pool.apply_async(os.getpid, ()) # runs in *only* one process
print(res.get(timeout=1)) # prints the PID of that process
# launching multiple evaluations asynchronously *may* use more processes
multiple_results = [pool.apply_async(os.getpid, ()) for i in range(4)]
print([res.get(timeout=1) for res in multiple_results])
# make a single worker sleep for 10 seconds
res = pool.apply_async(time.sleep, (10,))
try:
print(res.get(timeout=1))
except TimeoutError:
print("We lacked patience and got a multiprocessing.TimeoutError")
print("For the moment, the pool remains available for more work")
# exiting the 'with'-block has stopped the pool
print("Now the pool is closed and no longer available")
Обратите внимание, что методы пула должен использовать только создавший его процесс.
Примечание
Для работы функций этого пакета дочерние процессы должны иметь возможность импортировать модуль __main__. Это рассматривается в разделе Рекомендации по программированию, но стоит упомянуть об этом и здесь. Это означает, что некоторые примеры, например примеры с multiprocessing.pool.Pool, не будут работать в интерактивном интерпретаторе. Например:
>>> from multiprocessing import Pool >>> p = Pool(5) >>> def f(x): ... return x*x ... >>> with p: ... p.map(f, [1,2,3]) Process PoolWorker-1: Process PoolWorker-2: Process PoolWorker-3: Traceback (most recent call last): Traceback (most recent call last): Traceback (most recent call last): AttributeError: Can't get attribute 'f' on <module '__main__' (<class '_frozen_importlib.BuiltinImporter'>)> AttributeError: Can't get attribute 'f' on <module '__main__' (<class '_frozen_importlib.BuiltinImporter'>)> AttributeError: Can't get attribute 'f' on <module '__main__' (<class '_frozen_importlib.BuiltinImporter'>)>
(Если вы попробуете это сделать, в действительности будут выведены три полные трассировки стека, перемешанные в полу случайном порядке, после чего, возможно, придётся каким-либо образом остановить родительский процесс.)
Справочник
Пакет multiprocessing в основном воспроизводит API модуля threading.
Process и исключения
-
class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None) -
Объекты Process представляют деятельность, выполняемую в отдельном процессе. Класс
Processимеет аналоги всех методов классаthreading.Thread.Конструктор всегда следует вызывать с именованными аргументами. group всегда должен быть
None; этот аргумент существует исключительно для совместимости сthreading.Thread. target — вызываемый объект, который будет вызван методомrun(). По умолчанию он равенNone, то есть ничего не вызывается. name — имя процесса (подробнее см.name). args — кортеж аргументов для вызова целевого объекта. kwargs — словарь именованных аргументов для вызова целевого объекта. Если указан, аргумент daemon, принимающий только значение по имени, задаёт флагdaemonпроцесса в значениеTrueилиFalse. Если указаноNone(значение по умолчанию), этот флаг наследуется от создающего процесса.По умолчанию целевому объекту target не передаются аргументы. Аргумент args, значение которого по умолчанию —
(), можно использовать, чтобы указать список или кортеж аргументов, передаваемых объекту target.Если подкласс переопределяет конструктор, он должен убедиться, что конструктор базового класса (
super().__init__()) вызывается до выполнения любых других действий с процессом.Примечание
Как правило, все аргументы
Processдолжны быть сериализуемыми с помощью pickle. Эта проблема часто возникает при попытке создатьProcessили использоватьconcurrent.futures.ProcessPoolExecutorиз REPL, если целевая функция target определена локально.Передача вызываемого объекта, определённого в текущем сеансе REPL, приводит к аварийному завершению дочернего процесса из-за неперехваченного исключения
AttributeErrorпри запуске: объект target должен быть определён в импортируемом модуле, чтобы его можно было загрузить при десериализации pickle.Пример этой неперехватываемой ошибки в дочернем процессе:
>>> import multiprocessing as mp >>> def knigit(): ... print("Ni!") ... >>> process = mp.Process(target=knigit) >>> process.start() >>> Traceback (most recent call last): File ".../multiprocessing/spawn.py", line ..., in spawn_main File ".../multiprocessing/spawn.py", line ..., in _main AttributeError: module '__main__' has no attribute 'knigit' >>> process <SpawnProcess name='SpawnProcess-1' pid=379473 parent=378707 stopped exitcode=1>См. Методы запуска spawn и forkserver. Это ограничение не действует при использовании метода запуска
"fork", однако начиная с Python3.14этот метод больше не используется по умолчанию ни на одной платформе. См. Контексты и методы запуска. См. также gh-132898.Изменено в версии 3.3: Добавлен параметр daemon.
-
run() -
Метод, представляющий деятельность процесса.
Этот метод можно переопределить в подклассе. Стандартный метод
run()вызывает вызываемый объект, переданный при создании объекта в качестве аргумента target, если такой объект указан, и передаёт ему позиционные и именованные аргументы, взятые соответственно из аргументов args и kwargs.Передача списка или кортежа в качестве аргумента args при вызове
Processдаёт тот же результат.Пример:
>>> from multiprocessing import Process >>> p = Process(target=print, args=[1]) >>> p.run() 1 >>> p = Process(target=print, args=(1,)) >>> p.run() 1
-
start() -
Запускает выполнение процесса.
Для каждого объекта процесса этот метод можно вызвать не более одного раза. Он обеспечивает вызов метода
run()объекта в отдельном процессе.
-
join([timeout]) -
Если необязательный аргумент timeout равен
None(значение по умолчанию), метод блокируется до завершения процесса, для которого вызван методjoin(). Если timeout — положительное число, метод блокируется не дольше указанного количества секунд. Обратите внимание: метод возвращаетNone, если процесс завершился или истёк тайм-аут. Чтобы определить, завершился ли процесс, проверьте егоexitcode.К процессу можно присоединяться многократно.
Процесс не может присоединиться к самому себе, так как это приведёт к взаимной блокировке. Попытка присоединиться к процессу до его запуска считается ошибкой.
-
name -
Имя процесса. Имя представляет собой строку, используемую только для идентификации. Оно не имеет семантического значения. Нескольким процессам можно задать одно и то же имя.
Начальное имя задаётся конструктором. Если конструктору не передано явное имя, формируется имя вида «Process-N1:N2:…:Nk», где каждый Nk — N-й дочерний процесс своего родителя.
-
is_alive() -
Возвращает признак того, что процесс активен.
Условно можно считать, что объект процесса активен с момента возврата метода
start()до завершения дочернего процесса.
-
daemon -
Флаг демона процесса, логическое значение. Его необходимо установить до вызова
start().Начальное значение наследуется от создающего процесса.
При завершении процесса он пытается завершить все свои дочерние процессы-демоны.
Обратите внимание: процесс-демон не может создавать дочерние процессы. Иначе при завершении родительского процесса процесс-демон оставил бы свои дочерние процессы осиротевшими. Кроме того, это не демоны Unix и не службы: это обычные процессы, которые завершаются (без присоединения к ним), когда завершаются процессы, не являющиеся демонами.
Помимо API класса
threading.Thread, объектыProcessтакже поддерживают следующие атрибуты и методы:-
pid -
Возвращает идентификатор процесса. До запуска процесса возвращается
None.
-
exitcode -
Код завершения дочернего процесса. Возвращается
None, если процесс ещё не завершился.Если метод
run()дочернего процесса завершился нормально, код завершения будет равен 0. Если процесс завершился вызовомsys.exit()с целочисленным аргументом N, код завершения будет равен N.Если дочерний процесс завершился из-за исключения, не перехваченного в
run(), код завершения будет равен 1. Если процесс был завершён сигналом N, код завершения будет равен отрицательному значению -N.
-
authkey -
Ключ аутентификации процесса (строка байтов).
При инициализации
multiprocessingглавному процессу назначается случайная строка с помощьюos.urandom().При создании объекта
Processон наследует ключ аутентификации родительского процесса, однако его можно изменить, присвоив атрибутуauthkeyдругую строку байтов.См. Ключи аутентификации.
-
sentinel -
Числовой дескриптор системного объекта, который переходит в состояние «готов», когда процесс завершается.
Это значение можно использовать, если требуется одновременно ожидать несколько событий с помощью
multiprocessing.connection.wait(). В противном случае проще вызватьjoin().В Windows это дескриптор ОС, который можно использовать с семейством вызовов API
WaitForSingleObjectиWaitForMultipleObjects. В POSIX это файловый дескриптор, который можно использовать с примитивами модуляselect.Добавлено в версии 3.3.
-
terminate() -
Завершает процесс. В POSIX для этого используется сигнал
SIGTERM; в Windows используетсяTerminateProcess(). Обратите внимание, что обработчики выхода, блоки finally и т. п. выполнены не будут.Обратите внимание: дочерние процессы данного процесса не будут завершены — они просто станут осиротевшими.
Предупреждение
Если этот метод вызывается, когда связанный процесс использует канал или очередь, они могут быть повреждены и стать непригодными для использования другими процессами. Аналогично, если процесс захватил блокировку, семафор и т. п., его завершение может привести к взаимной блокировке других процессов.
-
kill() -
То же, что и
terminate(), но в POSIX используется сигналSIGKILL.Добавлено в версии 3.7.
-
close() -
Закрывает объект
Process, освобождая все связанные с ним ресурсы. Если базовый процесс всё ещё выполняется, возникает исключениеValueError. После успешного возврата изclose()большинство других методов и атрибутов объектаProcessбудет вызывать исключениеValueError.Добавлено в версии 3.7.
Обратите внимание: методы
start(),join(),is_alive(),terminate()иexitcodeдолжен вызывать только процесс, создавший объект процесса.Пример использования некоторых методов класса
Process:>>> import multiprocessing, time, signal >>> mp_context = multiprocessing.get_context('spawn') >>> p = mp_context.Process(target=time.sleep, args=(1000,)) >>> print(p, p.is_alive()) <...Process ... initial> False >>> p.start() >>> print(p, p.is_alive()) <...Process ... started> True >>> p.terminate() >>> time.sleep(0.1) >>> print(p, p.is_alive()) <...Process ... stopped exitcode=-SIGTERM> False >>> p.exitcode == -signal.SIGTERM True -
-
exception multiprocessing.ProcessError -
Базовый класс всех исключений
multiprocessing.
-
exception multiprocessing.BufferTooShort -
Исключение, которое вызывает
Connection.recv_bytes_into(), если переданный объект буфера слишком мал для прочитанного сообщения.Если
eявляется экземпляромBufferTooShort, тоe.args[0]вернёт сообщение в виде строки байтов.
-
exception multiprocessing.AuthenticationError -
Возникает при ошибке аутентификации.
-
exception multiprocessing.TimeoutError -
Возникает при истечении тайм-аута в методах, поддерживающих тайм-аут.
Каналы и очереди
При использовании нескольких процессов для взаимодействия между ними обычно применяют передачу сообщений, избегая необходимости использовать примитивы синхронизации, например блокировки.
Для передачи сообщений можно использовать Pipe() (для соединения двух процессов) или очередь (которая позволяет работать нескольким производителям и потребителям).
Типы Queue, SimpleQueue и JoinableQueue представляют собой очереди FIFO с несколькими производителями и потребителями, созданные по образцу класса queue.Queue из стандартной библиотеки. Они отличаются тем, что в Queue отсутствуют методы task_done() и join(), добавленные в класс queue.Queue в Python 2.5.
При использовании JoinableQueue для каждой задачи, извлечённой из очереди, необходимо вызывать JoinableQueue.task_done(), иначе семафор, используемый для подсчёта количества незавершённых задач, может переполниться и вызвать исключение.
Одно из отличий от других реализаций очередей в Python состоит в том, что очереди multiprocessing сериализуют все помещаемые в них объекты с помощью pickle. Объект, возвращаемый методом get, является заново созданным объектом, который не использует общую память с исходным объектом.
Обратите внимание, что общую очередь можно также создать с помощью объекта-менеджера — см. Менеджеры.
Примечание
multiprocessing использует стандартные исключения queue.Empty и queue.Full для сообщения об истечении времени ожидания. Они недоступны в пространстве имён multiprocessing, поэтому их нужно импортировать из queue.
Примечание
При помещении объекта в очередь он сериализуется, а фоновый поток позднее сбрасывает сериализованные данные в базовый канал. Это имеет некоторые несколько неожиданные последствия, но на практике не должно создавать трудностей. Если же они вас действительно беспокоят, можно вместо этого использовать очередь, созданную с помощью менеджера.
- После помещения объекта в пустую очередь может пройти бесконечно малое время, прежде чем метод
empty()этой очереди вернётFalse, а вызовget_nowait()сможет завершиться, не вызвав исключениеqueue.Empty. - Если несколько процессов помещают объекты в очередь, на другом конце они могут быть получены не по порядку. Однако объекты, помещённые в очередь одним и тем же процессом, всегда будут получены в ожидаемом порядке относительно друг друга.
Предупреждение
Если процесс завершается с помощью Process.terminate() или os.kill() во время работы с Queue, данные в очереди, скорее всего, будут повреждены. Впоследствии при попытке использовать очередь в любом другом процессе может возникнуть исключение.
Предупреждение
Как упоминалось выше, если дочерний процесс поместил элементы в очередь (и не вызвал JoinableQueue.cancel_join_thread), этот процесс не завершится, пока все буферизованные элементы не будут сброшены в канал.
Это означает, что попытка присоединиться к этому процессу может привести к взаимной блокировке, если только вы не уверены, что все помещённые в очередь элементы были обработаны. Аналогичным образом, если дочерний процесс не является демоническим, родительский процесс может зависнуть при выходе, когда попытается присоединиться ко всем своим недемоническим дочерним процессам.
Обратите внимание, что у очереди, созданной с помощью менеджера, такой проблемы нет. См. Рекомендации по программированию.
Пример использования очередей для межпроцессного взаимодействия см. в разделе Примеры.
-
multiprocessing.Pipe([duplex]) -
Возвращает пару
(conn1, conn2)объектовConnection, представляющих концы канала.Если duplex имеет значение
True(по умолчанию), канал является двунаправленным. Если duplex имеет значениеFalse, канал является однонаправленным:conn1можно использовать только для получения сообщений, аconn2— только для их отправки.Метод
send()сериализует объект с помощьюpickle, аrecv()заново создаёт объект.
-
class multiprocessing.Queue([maxsize]) -
Возвращает очередь, совместно используемую процессами и реализованную с помощью канала и нескольких блокировок/семафоров. Когда процесс впервые помещает элемент в очередь, запускается поток-обработчик, который переносит объекты из буфера в канал.
Для сообщения об истечении времени ожидания вызываются стандартные исключения
queue.Emptyиqueue.Fullиз модуляqueueстандартной библиотеки.Queueреализует все методыqueue.Queue, кромеtask_done()иjoin().-
qsize() -
Возвращает приблизительный размер очереди. Из-за семантики многопоточности/многопроцессности это число ненадёжно.
Обратите внимание, что на таких платформах, как macOS, этот метод может вызвать
NotImplementedError, посколькуsem_getvalue()не реализован.
-
empty() -
Возвращает
True, если очередь пуста, иFalseв противном случае. Из-за семантики многопоточности/многопроцессности этот результат ненадёжен.Для закрытых очередей может вызвать
OSError(не гарантируется).
-
full() -
Возвращает
True, если очередь заполнена, иFalseв противном случае. Из-за семантики многопоточности/многопроцессности этот результат ненадёжен.
-
put(obj[, block[, timeout]]) -
Помещает obj в очередь. Если необязательный аргумент block имеет значение
True(по умолчанию), а timeout —None(по умолчанию), метод при необходимости ожидает появления свободного места. Если timeout — положительное число, метод ожидает не более timeout секунд и вызывает исключениеqueue.Full, если за это время свободное место не появилось. В противном случае (если block имеет значениеFalse) элемент помещается в очередь, если свободное место доступно немедленно; иначе вызывается исключениеqueue.Full(в этом случае timeout игнорируется).Изменено в версии 3.8: Если очередь закрыта, вместо
AssertionErrorвызываетсяValueError.
-
put_nowait(obj) -
Эквивалентно
put(obj, False).
-
get([block[, timeout]]) -
Извлекает и возвращает элемент из очереди. Если необязательный аргумент block имеет значение
True(по умолчанию), а timeout —None(по умолчанию), метод при необходимости ожидает появления элемента. Если timeout — положительное число, метод ожидает не более timeout секунд и вызывает исключениеqueue.Empty, если за это время элемент не появился. В противном случае (если block имеет значениеFalse) метод возвращает элемент, если он доступен немедленно; иначе вызывается исключениеqueue.Empty(в этом случае timeout игнорируется).Изменено в версии 3.8: Если очередь закрыта, вместо
OSErrorвызываетсяValueError.
-
get_nowait() -
Эквивалентно
get(False).
У
multiprocessing.Queueесть несколько дополнительных методов, отсутствующих вqueue.Queue. Обычно эти методы не нужны в большинстве программ:-
close() -
Закрывает очередь и освобождает внутренние ресурсы.
После закрытия очередью больше нельзя пользоваться. Например, больше нельзя вызывать методы
get(),put()иempty().Фоновый поток завершит работу после сброса всех буферизованных данных в канал. Этот метод вызывается автоматически при сборке мусора для очереди.
-
join_thread() -
Ожидает завершения фонового потока. Этот метод можно использовать только после вызова
close(). Он блокирует выполнение до завершения фонового потока, гарантируя, что все данные из буфера сброшены в канал.По умолчанию при выходе процесс, не создавший очередь, пытается присоединиться к её фоновому потоку. Процесс может вызвать
cancel_join_thread(), чтобы методjoin_thread()ничего не делал.
-
cancel_join_thread() -
Предотвращает блокировку метода
join_thread(). В частности, это предотвращает автоматическое присоединение к фоновому потоку при завершении процесса — см.join_thread().Более подходящим именем для этого метода могло бы быть
allow_exit_without_flush(). Вероятно, это приведёт к потере данных, помещённых в очередь, и почти наверняка вам не понадобится его использовать. Он предназначен лишь для тех случаев, когда нужно немедленно завершить текущий процесс, не дожидаясь сброса данных из очереди в базовый канал, и потеря данных не имеет значения.
Примечание
Для работы этого класса операционная система хоста должна поддерживать работающую реализацию общих семафоров. Если такой реализации нет, функциональность класса будет отключена, а попытка создать экземпляр
Queueприведёт кImportError. Дополнительные сведения см. в bpo-3770. То же относится и к любому из перечисленных ниже специализированных типов очередей. -
-
class multiprocessing.SimpleQueue -
Это упрощённый тип
Queue, очень похожий на заблокированныйPipe.-
close() -
Закрывает очередь и освобождает внутренние ресурсы.
После закрытия очередью больше нельзя пользоваться. Например, больше нельзя вызывать методы
get(),put()иempty().Добавлено в версии 3.9.
-
empty() -
Возвращает
True, если очередь пуста, иFalseв противном случае.Если SimpleQueue закрыта, всегда вызывает
OSError.
-
get() -
Извлекает и возвращает элемент из очереди.
-
put(item) -
Помещает item в очередь.
-
-
class multiprocessing.JoinableQueue([maxsize]) -
JoinableQueue— подклассQueue, представляющий собой очередь с дополнительными методамиtask_done()иjoin().-
task_done() -
Сообщает, что ранее помещённая в очередь задача завершена. Используется потребителями очереди. Для каждого вызова
get(), используемого для получения задачи, последующий вызовtask_done()сообщает очереди, что обработка задачи завершена.Если вызов
join()в данный момент заблокирован, он возобновится, когда будут обработаны все элементы (то есть когда для каждого элемента, помещённого в очередь с помощьюput(), будет получен вызовtask_done()).Вызывает
ValueError, если число вызовов превышает число элементов, помещённых в очередь.
-
join() -
Блокирует выполнение, пока все элементы очереди не будут извлечены и обработаны.
Счётчик незавершённых задач увеличивается при добавлении элемента в очередь. Счётчик уменьшается при вызове потребителем метода
task_done(), который сообщает, что элемент был извлечён и вся работа с ним завершена. Когда счётчик незавершённых задач становится равен нулю, вызовjoin()разблокируется.
-
Разное
-
multiprocessing.active_children() -
Возвращает список всех активных дочерних процессов текущего процесса.
Вызов этой функции имеет побочный эффект: она «присоединяет» все процессы, которые уже завершились.
-
multiprocessing.cpu_count() -
Возвращает количество процессоров в системе.
Это число не равно количеству процессоров, которые может использовать текущий процесс. Количество доступных процессоров можно получить с помощью
os.process_cpu_count()(илиlen(os.sched_getaffinity(0))).Если количество процессоров не удаётся определить, возбуждается исключение
NotImplementedError.См. также
Изменено в версии 3.13: Возвращаемое значение также можно переопределить с помощью флага
-X cpu_countили переменнойPYTHON_CPU_COUNT, поскольку эта функция является лишь обёрткой над API модуляosдля получения количества процессоров.
-
multiprocessing.current_process() -
Возвращает объект
Process, соответствующий текущему процессу.Аналог функции
threading.current_thread().
-
multiprocessing.parent_process() -
Возвращает объект
Process, соответствующий родительскому процессу дляcurrent_process(). Для главного процессаparent_processбудет иметь значениеNone.Добавлено в версии 3.8.
-
multiprocessing.freeze_support() -
Добавляет поддержку для программ, использующих
multiprocessing, которые были заморожены для создания исполняемого файла. (Работа проверена с py2exe, PyInstaller и cx_Freeze.)Эту функцию нужно вызвать сразу после строки
if __name__ == '__main__'главного модуля. Например:from multiprocessing import Process, freeze_support def f(): print('hello world!') if __name__ == '__main__': freeze_support() Process(target=f).start()Если строка
freeze_support()опущена, попытка запустить замороженный исполняемый файл приведёт к исключениюRuntimeError.Вызов
freeze_support()не оказывает никакого эффекта, если метод запуска не равен spawn. Кроме того, если модуль выполняется обычным образом интерпретатором Python (программа не была заморожена),freeze_support()не оказывает никакого эффекта.
-
multiprocessing.get_all_start_methods() -
Возвращает список поддерживаемых методов запуска; первый из них используется по умолчанию. Возможные методы запуска:
'fork','spawn'и'forkserver'. Не все платформы поддерживают все методы. См. раздел Контексты и методы запуска.Добавлено в версии 3.4.
-
multiprocessing.get_context(method=None) -
Возвращает объект контекста с теми же атрибутами, что и у модуля
multiprocessing.Если method равен
None, возвращается контекст по умолчанию. Обратите внимание: если глобальный метод запуска ещё не установлен, он будет установлен в значение по умолчанию. В противном случае method должен быть равен'fork','spawn'или'forkserver'. Если указанный метод запуска недоступен, возбуждается исключениеValueError. См. раздел Контексты и методы запуска.Добавлено в версии 3.4.
-
multiprocessing.get_start_method(allow_none=False) -
Возвращает название метода, используемого для запуска процессов.
Если глобальный метод запуска ещё не установлен и allow_none равен
False, устанавливается метод запуска по умолчанию и возвращается его название. Если метод запуска ещё не установлен и allow_none равенTrue, возвращаетсяNone.Возвращаемое значение может быть
'fork','spawn','forkserver'илиNone. См. раздел Контексты и методы запуска.Добавлено в версии 3.4.
Изменено в версии 3.8: В macOS метод запуска spawn теперь используется по умолчанию. Метод запуска fork следует считать небезопасным, поскольку он может приводить к аварийному завершению подпроцесса. См. bpo-33725.
-
multiprocessing.set_executable(executable) -
Задаёт путь к интерпретатору Python, который будет использоваться при запуске дочернего процесса. (По умолчанию используется
sys.executable.) Для встраиваемых приложений, вероятно, потребуется выполнить что-то вродеset_executable(os.path.join(sys.exec_prefix, 'pythonw.exe'))
прежде чем создавать дочерние процессы.
Изменено в версии 3.4: Теперь поддерживается в POSIX при использовании метода запуска
'spawn'.Изменено в версии 3.11: Принимает объект, подобный пути.
-
multiprocessing.set_forkserver_preload(module_names) -
Задаёт список имён модулей, которые главный процесс forkserver должен попытаться импортировать, чтобы их уже импортированное состояние наследовалось процессами, созданными с помощью fork. Любые исключения
ImportError, возникающие при этом, игнорируются без уведомления. Это можно использовать для повышения производительности, избегая повторения одних и тех же операций в каждом процессе.Чтобы это работало, функцию необходимо вызвать до запуска процесса forkserver (до создания
Poolили запускаProcess).Имеет смысл только при использовании метода запуска
'forkserver'. См. раздел Контексты и методы запуска.Добавлено в версии 3.4.
-
multiprocessing.set_start_method(method, force=False) -
Задаёт метод, который следует использовать для запуска дочерних процессов. Аргумент method может быть равен
'fork','spawn'или'forkserver'. Возбуждает исключениеRuntimeError, если метод запуска уже установлен и force не равенTrue. Если method равенNoneи force равенTrue, метод запуска устанавливается вNone. Если method равенNoneи force равенFalse, контекст устанавливается в значение по умолчанию.Обратите внимание: эту функцию следует вызывать не более одного раза, и её вызов должен быть защищён условием
if __name__ == '__main__'в главном модуле.См. раздел Контексты и методы запуска.
Добавлено в версии 3.4.
Примечание
В multiprocessing нет аналогов функций threading.active_count(), threading.enumerate(), threading.settrace(), threading.setprofile(), threading.Timer или threading.local.
Объекты соединения
Объекты соединения позволяют отправлять и получать объекты, которые можно сериализовать с помощью pickle, или строки. Их можно рассматривать как соединённые сокеты, ориентированные на сообщения.
Объекты соединения обычно создаются с помощью Pipe — см. также раздел Слушатели и клиенты.
-
class multiprocessing.connection.Connection -
-
send(obj) -
Отправляет объект на другой конец соединения; прочитать его можно с помощью
recv().Объект должен поддерживать сериализацию с помощью pickle. При обработке очень больших объектов pickle (примерно от 32 МиБ, хотя это зависит от ОС) может возникнуть исключение
ValueError.
-
recv() -
Возвращает объект, отправленный с другого конца соединения с помощью
send(). Блокирует выполнение, пока не появятся данные для получения. Возбуждает исключениеEOFError, если данные для получения закончились, а другой конец соединения был закрыт.
-
fileno() -
Возвращает файловый дескриптор или дескриптор, используемый соединением.
-
close() -
Закрывает соединение.
Вызывается автоматически при сборке мусора для объекта соединения.
-
poll([timeout]) -
Возвращает значение, указывающее, доступны ли данные для чтения.
Если параметр timeout не указан, функция возвращает результат немедленно. Если timeout — число, оно задаёт максимальное время блокировки в секундах. Если timeout равен
None, используется бесконечное время ожидания.Обратите внимание: одновременно можно опрашивать несколько объектов соединения с помощью
multiprocessing.connection.wait().
-
send_bytes(buffer[, offset[, size]]) -
Отправляет байтовые данные из объекта, подобного bytes, как единое сообщение.
Если задан параметр offset, данные считываются из указанной позиции в buffer. Если задан параметр size, из буфера будет считано указанное количество байтов. При обработке очень больших буферов (примерно от 32 МиБ, хотя это зависит от ОС) может возникнуть исключение
ValueError.
-
recv_bytes([maxlength]) -
Возвращает полное сообщение с байтовыми данными, отправленное с другого конца соединения, в виде строки. Блокирует выполнение, пока не появятся данные для получения. Возбуждает исключение
EOFError, если данные для получения закончились, а другой конец соединения был закрыт.Если задан параметр maxlength и длина сообщения превышает maxlength, возбуждается исключение
OSError, и соединение становится недоступным для чтения.
-
recv_bytes_into(buffer[, offset]) -
Считывает в buffer полное сообщение с байтовыми данными, отправленное с другого конца соединения, и возвращает количество байтов в сообщении. Блокирует выполнение, пока не появятся данные для получения. Возбуждает исключение
EOFError, если данные для получения закончились, а другой конец соединения был закрыт.buffer должен быть доступным для записи объектом, подобным bytes. Если задан параметр offset, сообщение будет записано в буфер начиная с указанной позиции. Смещение должно быть неотрицательным целым числом, меньшим длины buffer (в байтах).
Если буфер слишком короткий, возбуждается исключение
BufferTooShort, а полное сообщение доступно какe.args[0], гдеe— экземпляр исключения.
Изменено в версии 3.3: Теперь сами объекты соединения можно передавать между процессами с помощью
Connection.send()иConnection.recv().Теперь объекты соединения также поддерживают протокол управления контекстом — см. раздел Типы менеджеров контекста.
__enter__()возвращает объект соединения, а__exit__()вызываетclose(). -
Например:
>>> from multiprocessing import Pipe
>>> a, b = Pipe()
>>> a.send([1, 'hello', None])
>>> b.recv()
[1, 'hello', None]
>>> b.send_bytes(b'thank you')
>>> a.recv_bytes()
b'thank you'
>>> import array
>>> arr1 = array.array('i', range(5))
>>> arr2 = array.array('i', [0] * 10)
>>> a.send_bytes(arr1)
>>> count = b.recv_bytes_into(arr2)
>>> assert count == len(arr1) * arr1.itemsize
>>> arr2
array('i', [0, 1, 2, 3, 4, 0, 0, 0, 0, 0])
Предупреждение
Метод Connection.recv() автоматически десериализует полученные данные с помощью pickle, что может представлять угрозу безопасности, если вы не доверяете процессу, отправившему сообщение.
Поэтому, если объект соединения не был создан с помощью Pipe(), методы recv() и send() следует использовать только после выполнения какой-либо аутентификации. См. раздел Ключи аутентификации.
Предупреждение
Если процесс завершается принудительно во время чтения из канала или записи в него, данные в канале, скорее всего, будут повреждены, поскольку может оказаться невозможно определить границы сообщений.
Примитивы синхронизации
В целом примитивы синхронизации не так необходимы в многопроцессной программе, как в многопоточной. См. документацию по модулю threading.
Обратите внимание, что примитивы синхронизации также можно создать с помощью объекта-менеджера — см. раздел Менеджеры.
-
class multiprocessing.Barrier(parties[, action[, timeout]]) -
Объект барьера: копия
threading.Barrier.Добавлено в версии 3.3.
-
class multiprocessing.BoundedSemaphore([value]) -
Объект ограниченного семафора: близкий аналог
threading.BoundedSemaphore.Есть лишь одно отличие от близкого аналога: первый аргумент метода
acquireназывается block, что соответствуетLock.acquire().Примечание
В macOS этот объект неотличим от
Semaphore, посколькуsem_getvalue()не реализован на этой платформе.
-
class multiprocessing.Condition([lock]) -
Условная переменная: псевдоним для
threading.Condition.Если указан lock, это должен быть объект
LockилиRLockизmultiprocessing.Изменено в версии 3.3: Добавлен метод
wait_for().
-
class multiprocessing.Event -
Копия
threading.Event.
-
class multiprocessing.Lock -
Нерекурсивный объект блокировки: близкий аналог
threading.Lock. После того как процесс или поток захватил блокировку, последующие попытки захватить её из любого процесса или потока будут заблокированы до тех пор, пока она не будет освобождена; освободить её может любой процесс или поток. Понятия и поведениеthreading.Lockприменительно к потокам воспроизведены здесь вmultiprocessing.Lockприменительно как к процессам, так и к потокам, за исключением отмеченных ниже случаев.Обратите внимание, что
Lockна самом деле является фабричной функцией, которая возвращает экземплярmultiprocessing.synchronize.Lock, инициализированный с контекстом по умолчанию.Lockподдерживает протокол контекстного менеджера, поэтому его можно использовать в инструкцияхwith.-
acquire(block=True, timeout=None) -
Захватить блокировку в блокирующем или неблокирующем режиме.
Если аргумент block имеет значение
True(по умолчанию), вызов метода будет заблокирован до тех пор, пока блокировка не окажется свободной, после чего она будет установлена в состояние блокировки, а метод вернётTrue. Обратите внимание, что название первого аргумента отличается от названия вthreading.Lock.acquire().Если аргумент block имеет значение
False, вызов метода не блокируется. Если блокировка в данный момент захвачена, возвращаетсяFalse; в противном случае блокировка захватывается и возвращаетсяTrue.Если методу передано положительное значение с плавающей точкой для timeout, ожидание блокируется не дольше указанного в timeout количества секунд, пока блокировку не удастся захватить. Вызовы с отрицательным значением timeout эквивалентны вызовам с timeout, равным нулю. Вызовы со значением timeout
None(по умолчанию) задают бесконечный период ожидания. Обратите внимание, что обработка отрицательных значений или значенийNoneдля timeout отличается от реализованного поведения вthreading.Lock.acquire(). Аргумент timeout не имеет практического значения, если аргумент block имеет значениеFalse, и поэтому игнорируется. ВозвращаетTrue, если блокировка была захвачена, илиFalse, если истёк период ожидания.
-
release() -
Освободить блокировку. Этот метод может быть вызван из любого процесса или потока, а не только из того процесса или потока, который изначально захватил блокировку.
Поведение такое же, как у
threading.Lock.release(), за исключением того, что при вызове для незахваченной блокировки возникает исключениеValueError.
-
-
class multiprocessing.RLock -
Рекурсивный объект блокировки: близкий аналог
threading.RLock. Рекурсивную блокировку должен освободить процесс или поток, который её захватил. После того как процесс или поток захватил рекурсивную блокировку, тот же процесс или поток может захватывать её повторно без блокировки; этот процесс или поток должен освободить её столько раз, сколько он её захватил.Обратите внимание, что
RLockна самом деле является фабричной функцией, которая возвращает экземплярmultiprocessing.synchronize.RLock, инициализированный с контекстом по умолчанию.RLockподдерживает протокол контекстного менеджера, поэтому его можно использовать в инструкцияхwith.-
acquire(block=True, timeout=None) -
Захватить блокировку в блокирующем или неблокирующем режиме.
Если аргумент block имеет значение
True, ожидание блокируется до тех пор, пока блокировка не станет свободной (то есть не будет принадлежать ни одному процессу или потоку), если только она уже не принадлежит текущему процессу или потоку. Затем текущий процесс или поток становится владельцем блокировки (если ещё не является им), а уровень рекурсии внутри блокировки увеличивается на единицу; в результате возвращаетсяTrue. Обратите внимание, что поведение этого первого аргумента отличается от реализацииthreading.RLock.acquire()по нескольким аспектам, начиная с названия самого аргумента.Если аргумент block имеет значение
False, ожидание не блокируется. Если блокировка уже захвачена (и, следовательно, принадлежит) другому процессу или потоку, текущий процесс или поток не становится её владельцем, а уровень рекурсии внутри блокировки не изменяется; в результате возвращаетсяFalse. Если блокировка свободна, текущий процесс или поток становится её владельцем, а уровень рекурсии увеличивается; в результате возвращаетсяTrue.Использование аргумента timeout и его поведение такие же, как у
Lock.acquire(). Обратите внимание, что некоторые аспекты поведения timeout отличаются от реализованного поведения вthreading.RLock.acquire().
-
release() -
Освободить блокировку, уменьшив уровень рекурсии. Если после уменьшения уровень рекурсии равен нулю, блокировка переводится в свободное состояние (то есть не принадлежит ни одному процессу или потоку); если другие процессы или потоки ожидают освобождения блокировки, ровно одному из них разрешается продолжить выполнение. Если после уменьшения уровень рекурсии остаётся ненулевым, блокировка остаётся захваченной и принадлежит вызвавшему метод процессу или потоку.
Вызывайте этот метод, только если вызывающий процесс или поток владеет блокировкой. Если метод вызван процессом или потоком, не являющимся владельцем, либо блокировка свободна (не принадлежит никому), возникает исключение
AssertionError. Обратите внимание, что тип исключения, возникающего в этой ситуации, отличается от реализованного поведения вthreading.RLock.release().
-
-
class multiprocessing.Semaphore([value]) -
Объект семафора: близкий аналог
threading.Semaphore.Есть лишь одно отличие от близкого аналога: первый аргумент метода
acquireназывается block, что соответствуетLock.acquire().
Примечание
В macOS sem_timedwait не поддерживается, поэтому вызов acquire() с тайм-аутом имитирует поведение этой функции с помощью цикла с задержкой.
Примечание
Для работы некоторых функций этого пакета требуется исправная реализация общих семафоров в операционной системе. Если такой реализации нет, модуль multiprocessing.synchronize будет отключён, а попытки его импортировать приведут к исключению ImportError. Дополнительные сведения см. в bpo-3770.
Менеджеры
Менеджеры предоставляют способ создавать данные, которыми можно совместно пользоваться в разных процессах, в том числе через сеть между процессами, работающими на разных компьютерах. Объект менеджера управляет серверным процессом, который управляет общими объектами. Другие процессы могут обращаться к общим объектам с помощью прокси-объектов.
-
multiprocessing.Manager() -
Возвращает запущенный объект
SyncManager, который можно использовать для совместного использования объектов между процессами. Возвращаемый объект менеджера соответствует порождённому дочернему процессу и имеет методы, создающие общие объекты и возвращающие соответствующие прокси-объекты.
Процессы менеджера завершаются, как только они удаляются сборщиком мусора или завершается их родительский процесс. Классы менеджеров определены в модуле multiprocessing.managers:
-
class multiprocessing.managers.BaseManager(address=None, authkey=None, serializer='pickle', ctx=None, *, shutdown_timeout=1.0) -
Создаёт объект BaseManager.
После создания следует вызвать
start()илиget_server().serve_forever(), чтобы убедиться, что объект менеджера ссылается на запущенный процесс менеджера.address — адрес, на котором процесс менеджера ожидает новые подключения. Если address имеет значение
None, выбирается произвольный адрес.authkey — ключ аутентификации, который используется для проверки допустимости входящих подключений к серверному процессу. Если authkey имеет значение
None, используетсяcurrent_process().authkey. В противном случае используется authkey; это должна быть строка байтов.serializer должен иметь значение
'pickle'(используется сериализацияpickle) или'xmlrpclib'(используется сериализацияxmlrpc.client).ctx — объект контекста или
None(используется текущий контекст). См. функциюget_context().shutdown_timeout — время ожидания в секундах, в течение которого процесс, используемый менеджером, должен завершиться при вызове метода
shutdown(). Если время ожидания истекает, процесс принудительно завершается. Если и принудительное завершение не удаётся выполнить за отведённое время, процесс уничтожается.Изменено в версии 3.11: Добавлен параметр shutdown_timeout.
-
start([initializer[, initargs]]) -
Запускает дочерний процесс для запуска менеджера. Если initializer не имеет значения
None, при запуске дочерний процесс вызоветinitializer(*initargs).
-
get_server() -
Возвращает объект
Server, представляющий фактический сервер, управляемый менеджером. ОбъектServerподдерживает методserve_forever():>>> from multiprocessing.managers import BaseManager >>> manager = BaseManager(address=('', 50000), authkey=b'abc') >>> server = manager.get_server() >>> server.serve_forever()Кроме того, у
Serverесть атрибутaddress.
-
connect() -
Подключает локальный объект менеджера к удалённому процессу менеджера:
>>> from multiprocessing.managers import BaseManager >>> m = BaseManager(address=('127.0.0.1', 50000), authkey=b'abc') >>> m.connect()
-
shutdown() -
Останавливает процесс, используемый менеджером. Этот метод доступен только в том случае, если для запуска серверного процесса использовался
start().Его можно вызывать несколько раз.
-
register(typeid[, callable[, proxytype[, exposed[, method_to_typeid[, create_method]]]]]) -
Метод класса, который можно использовать для регистрации типа или вызываемого объекта в классе менеджера.
typeid — «идентификатор типа», используемый для определения конкретного типа общего объекта. Это должна быть строка.
callable — вызываемый объект, используемый для создания объектов с данным идентификатором типа. Если экземпляр менеджера будет подключён к серверу с помощью метода
connect()или если аргумент create_method имеет значениеFalse, это можно оставить какNone.proxytype — подкласс
BaseProxy, используемый для создания прокси-объектов общих объектов с этим значением typeid. Если указаноNone, класс прокси создаётся автоматически.exposed используется для указания последовательности имён методов, к которым прокси-объектам этого типа typeid разрешено обращаться с помощью
BaseProxy._callmethod(). (Если exposed имеет значениеNone, вместо него используетсяproxytype._exposed_, если такой атрибут существует.) Если список открытых методов не задан, доступны все «открытые методы» общего объекта. (Здесь под «открытым методом» понимается любой атрибут, у которого есть метод__call__()и имя которого не начинается с'_'.)method_to_typeid — соответствие, используемое для указания типа возвращаемого значения тех открытых методов, которые должны возвращать прокси-объект. Оно сопоставляет имена методов строкам typeid. (Если method_to_typeid имеет значение
None, вместо него используетсяproxytype._method_to_typeid_, если такой атрибут существует.) Если имя метода не является ключом этого соответствия или если соответствие имеет значениеNone, объект, возвращённый методом, копируется по значению.create_method определяет, следует ли создать метод с именем typeid, с помощью которого можно указать серверному процессу создать новый общий объект и вернуть прокси-объект для него. По умолчанию используется значение
True.
У экземпляров
BaseManagerтакже есть одно свойство только для чтения:-
address -
Адрес, используемый менеджером.
Изменено в версии 3.3: Объекты менеджера поддерживают протокол управления контекстом — см. Типы менеджеров контекста.
__enter__()запускает серверный процесс (если он ещё не запущен), а затем возвращает объект менеджера.__exit__()вызываетshutdown().В предыдущих версиях
__enter__()не запускал серверный процесс менеджера, если тот ещё не был запущен. -
-
class multiprocessing.managers.SyncManager -
Подкласс
BaseManager, который можно использовать для синхронизации процессов. Объекты этого типа возвращаются функциейmultiprocessing.Manager().Его методы создают и возвращают прокси-объекты для ряда распространённых типов данных, которые требуется синхронизировать между процессами. В частности, это общие списки и словари.
-
Barrier(parties[, action[, timeout]]) -
Создаёт общий объект
threading.Barrierи возвращает прокси-объект для него.Добавлено в версии 3.3.
-
BoundedSemaphore([value]) -
Создаёт общий объект
threading.BoundedSemaphoreи возвращает прокси-объект для него.
-
Condition([lock]) -
Создаёт общий объект
threading.Conditionи возвращает прокси-объект для него.Если передан аргумент lock, он должен быть прокси-объектом для объекта
threading.Lockилиthreading.RLock.Изменено в версии 3.3: Добавлен метод
wait_for().
-
Event() -
Создаёт общий объект
threading.Eventи возвращает прокси-объект для него.
-
Lock() -
Создаёт общий объект
threading.Lockи возвращает прокси-объект для него.
-
Namespace() -
Создаёт общий объект
Namespaceи возвращает прокси-объект для него.
-
Queue([maxsize]) -
Создаёт общий объект
queue.Queueи возвращает прокси-объект для него.
-
RLock() -
Создаёт общий объект
threading.RLockи возвращает прокси-объект для него.
-
Semaphore([value]) -
Создаёт общий объект
threading.Semaphoreи возвращает прокси-объект для него.
-
Array(typecode, sequence) -
Создаёт массив и возвращает прокси-объект для него.
-
Value(typecode, value) -
Создаёт объект с доступным для записи атрибутом
valueи возвращает прокси-объект для него.
-
dict() - dict(mapping)
- dict(sequence)
-
Создаёт общий объект
dictи возвращает прокси-объект для него.
-
list() - list(sequence)
-
Создаёт общий объект
listи возвращает прокси-объект для него.
Изменено в версии 3.6: Общие объекты могут быть вложенными. Например, общий контейнер, такой как общий список, может содержать другие общие объекты, которыми будет управлять и которые будет синхронизировать
SyncManager. -
-
class multiprocessing.managers.Namespace -
Тип, который можно зарегистрировать в
SyncManager.У объекта пространства имён нет открытых методов, но есть доступные для записи атрибуты. Его представление показывает значения атрибутов.
Однако при использовании прокси-объекта для пространства имён атрибут, начинающийся с
'_', будет атрибутом прокси-объекта, а не объектом, на который он ссылается:>>> mp_context = multiprocessing.get_context('spawn') >>> manager = mp_context.Manager() >>> Global = manager.Namespace() >>> Global.x = 10 >>> Global.y = 'hello' >>> Global._z = 12.3 # this is an attribute of the proxy >>> print(Global) Namespace(x=10, y='hello')
Настраиваемые менеджеры
Чтобы создать собственный менеджер, нужно создать подкласс BaseManager и использовать метод класса register() для регистрации новых типов или вызываемых объектов в классе менеджера. Например:
from multiprocessing.managers import BaseManager
class MathsClass:
def add(self, x, y):
return x + y
def mul(self, x, y):
return x * y
class MyManager(BaseManager):
pass
MyManager.register('Maths', MathsClass)
if __name__ == '__main__':
with MyManager() as manager:
maths = manager.Maths()
print(maths.add(4, 3)) # prints 7
print(maths.mul(7, 8)) # prints 56
Использование удалённого менеджера
Можно запустить сервер менеджера на одном компьютере и обращаться к нему с клиентских компьютеров (если это разрешено соответствующими межсетевыми экранами).
Следующие команды создают сервер для одной общей очереди, к которой могут обращаться удалённые клиенты:
>>> from multiprocessing.managers import BaseManager
>>> from queue import Queue
>>> queue = Queue()
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue', callable=lambda:queue)
>>> m = QueueManager(address=('', 50000), authkey=b'abracadabra')
>>> s = m.get_server()
>>> s.serve_forever()
Один клиент может обратиться к серверу следующим образом:
>>> from multiprocessing.managers import BaseManager
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue')
>>> m = QueueManager(address=('foo.bar.org', 50000), authkey=b'abracadabra')
>>> m.connect()
>>> queue = m.get_queue()
>>> queue.put('hello')
К очереди может обратиться и другой клиент:
>>> from multiprocessing.managers import BaseManager
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue')
>>> m = QueueManager(address=('foo.bar.org', 50000), authkey=b'abracadabra')
>>> m.connect()
>>> queue = m.get_queue()
>>> queue.get()
'hello'
Локальные процессы также могут обращаться к этой очереди, используя приведённый выше код клиента для удалённого доступа:
>>> from multiprocessing import Process, Queue
>>> from multiprocessing.managers import BaseManager
>>> class Worker(Process):
... def __init__(self, q):
... self.q = q
... super().__init__()
... def run(self):
... self.q.put('local hello')
...
>>> queue = Queue()
>>> w = Worker(queue)
>>> w.start()
>>> class QueueManager(BaseManager): pass
...
>>> QueueManager.register('get_queue', callable=lambda: queue)
>>> m = QueueManager(address=('', 50000), authkey=b'abracadabra')
>>> s = m.get_server()
>>> s.serve_forever()
Прокси-объекты
Прокси-объект — это объект, который ссылается на общий объект, находящийся (предположительно) в другом процессе. Такой общий объект называется целевым объектом прокси-объекта. На один целевой объект могут ссылаться несколько прокси-объектов.
Прокси-объект имеет методы, вызывающие соответствующие методы целевого объекта (хотя через прокси-объект могут быть доступны не все методы целевого объекта). Таким образом, прокси-объект можно использовать так же, как и целевой объект:
>>> mp_context = multiprocessing.get_context('spawn')
>>> manager = mp_context.Manager()
>>> l = manager.list([i*i for i in range(10)])
>>> print(l)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> print(repr(l))
<ListProxy object, typeid 'list' at 0x...>
>>> l[4]
16
>>> l[2:5]
[4, 9, 16]
Обратите внимание: применение str() к прокси-объекту возвращает представление целевого объекта, тогда как применение repr() возвращает представление самого прокси-объекта.
Важная особенность прокси-объектов заключается в том, что их можно сериализовать с помощью pickle, поэтому их можно передавать между процессами. Следовательно, целевой объект может содержать прокси-объекты. Это позволяет вкладывать друг в друга управляемые списки, словари и другие прокси-объекты:
>>> a = manager.list()
>>> b = manager.list()
>>> a.append(b) # referent of a now contains referent of b
>>> print(a, b)
[<ListProxy object, typeid 'list' at ...>] []
>>> b.append('hello')
>>> print(a[0], b)
['hello'] ['hello']
Аналогичным образом прокси-объекты словарей и списков можно вкладывать друг в друга:
>>> l_outer = manager.list([ manager.dict() for i in range(2) ])
>>> d_first_inner = l_outer[0]
>>> d_first_inner['a'] = 1
>>> d_first_inner['b'] = 2
>>> l_outer[1]['c'] = 3
>>> l_outer[1]['z'] = 26
>>> print(l_outer[0])
{'a': 1, 'b': 2}
>>> print(l_outer[1])
{'c': 3, 'z': 26}
Если целевой объект содержит стандартные объекты list или dict (не являющиеся прокси-объектами), изменения этих изменяемых значений не будут передаваться через менеджер, поскольку прокси-объект не может определить, когда изменились содержащиеся в них значения. Однако запись значения в прокси-объект контейнера (которая вызывает __setitem__ для объекта-прокси) передаёт изменения через менеджер. Поэтому, чтобы эффективно изменить такой элемент, можно присвоить изменённое значение прокси-объекту контейнера повторно:
# create a list proxy and append a mutable object (a dictionary)
lproxy = manager.list()
lproxy.append({})
# now mutate the dictionary
d = lproxy[0]
d['a'] = 1
d['b'] = 2
# at this point, the changes to d are not yet synced, but by
# updating the dictionary, the proxy is notified of the change
lproxy[0] = d
В большинстве случаев этот подход, возможно, менее удобен, чем использование вложенных прокси-объектов, но он также демонстрирует возможность управлять синхронизацией.
Примечание
Типы прокси-объектов в multiprocessing не поддерживают сравнение по значению. Например:
>>> manager.list([1,2,3]) == [1,2,3] False
При сравнении следует просто использовать копию целевого объекта.
-
class multiprocessing.managers.BaseProxy -
Прокси-объекты являются экземплярами подклассов
BaseProxy.-
_callmethod(methodname[, args[, kwds]]) -
Вызывает метод целевого объекта прокси-объекта и возвращает результат.
Если
proxy— прокси-объект, целевой объект которого —obj, выражениеproxy._callmethod(methodname, args, kwds)
вычислит выражение
getattr(obj, methodname)(*args, **kwds)
в процессе менеджера.
Возвращаемое значение будет копией результата вызова или прокси-объектом нового общего объекта — см. описание аргумента method_to_typeid метода
BaseManager.register().Если при вызове возникает исключение, оно повторно вызывается методом
_callmethod(). Если в процессе менеджера возникает другое исключение, оно преобразуется в исключениеRemoteErrorи вызывается методом_callmethod().Обратите особое внимание: исключение будет вызвано, если methodname не был включён в список exposed.
Пример использования
_callmethod():>>> l = manager.list(range(10)) >>> l._callmethod('__len__') 10 >>> l._callmethod('__getitem__', (slice(2, 7),)) # equivalent to l[2:7] [2, 3, 4, 5, 6] >>> l._callmethod('__getitem__', (20,)) # equivalent to l[20] Traceback (most recent call last): ... IndexError: list index out of range
-
_getvalue() -
Возвращает копию целевого объекта.
Если целевой объект нельзя сериализовать с помощью pickle, будет вызвано исключение.
-
__repr__() -
Возвращает представление прокси-объекта.
-
__str__() -
Возвращает представление целевого объекта.
-
Очистка
Прокси-объект использует callback weakref, чтобы при удалении сборщиком мусора исключить его из списка менеджера, которому принадлежит его целевой объект.
Общий объект удаляется из процесса менеджера, когда на него больше не ссылается ни один прокси-объект.
Пулы процессов
Можно создать пул процессов, которые будут выполнять переданные ему задачи, с помощью класса Pool.
-
class multiprocessing.pool.Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]]) -
Объект пула процессов управляет пулом рабочих процессов, которым можно передавать задания. Он поддерживает асинхронное получение результатов с тайм-аутами и обратными вызовами, а также параллельную реализацию map.
processes — число используемых рабочих процессов. Если processes равно
None, используется число, возвращаемое функциейos.process_cpu_count().Если initializer не равно
None, каждый рабочий процесс вызоветinitializer(*initargs)при запуске.maxtasksperchild — число задач, которое рабочий процесс может выполнить, прежде чем завершит работу и будет заменён новым рабочим процессом, чтобы освободить неиспользуемые ресурсы. По умолчанию maxtasksperchild равно
None, то есть рабочие процессы работают столько же, сколько существует пул.context можно использовать, чтобы указать контекст, применяемый для запуска рабочих процессов. Обычно пул создаётся с помощью функции
multiprocessing.Pool()или методаPool()объекта контекста. В обоих случаях для context устанавливается соответствующее значение.Обратите внимание, что методы объекта пула должен вызывать только процесс, создавший этот пул.
Предупреждение
Объекты
multiprocessing.poolсодержат внутренние ресурсы, которыми необходимо надлежащим образом управлять (как и любыми другими ресурсами): используя пул как менеджер контекста или вручную вызываяclose()иterminate(). Если этого не сделать, процесс может зависнуть при завершении работы.Не следует полагаться на сборщик мусора в надежде, что он уничтожит пул: CPython не гарантирует вызов финализатора пула (подробнее см.
object.__del__()).Изменено в версии 3.2: Добавлен параметр maxtasksperchild.
Изменено в версии 3.4: Добавлен параметр context.
Изменено в версии 3.13: По умолчанию для processes используется
os.process_cpu_count()вместоos.cpu_count().Примечание
Рабочие процессы в
Poolобычно работают всё время, пока в очереди пула есть задания. В других системах (например, Apache, mod_wsgi и т. д.) часто используется следующий способ освободить ресурсы, занятые рабочими процессами: разрешить рабочему процессу в пуле выполнить ограниченное количество работы, после чего завершить его, очистить и запустить новый процесс на замену старому. Аргумент maxtasksperchild классаPoolпредоставляет эту возможность конечному пользователю.-
apply(func[, args[, kwds]]) -
Вызывает func с аргументами args и именованными аргументами kwds. Блокирует выполнение до получения результата. Поскольку этот вызов блокирует выполнение, для параллельного выполнения задач лучше подходит
apply_async(). Кроме того, func выполняется только в одном из рабочих процессов пула.
-
apply_async(func[, args[, kwds[, callback[, error_callback]]]]) -
Вариант метода
apply(), возвращающий объектAsyncResult.Если указан callback, он должен быть вызываемым объектом, принимающим один аргумент. Когда результат будет готов, для него будет вызван callback, если только вызов не завершился ошибкой; в этом случае вместо него вызывается error_callback.
Если указан error_callback, он должен быть вызываемым объектом, принимающим один аргумент. Если целевая функция завершится ошибкой, error_callback будет вызван с экземпляром исключения.
Обратные вызовы должны завершаться немедленно, иначе поток, обрабатывающий результаты, будет заблокирован.
-
map(func, iterable[, chunksize]) -
Параллельный эквивалент встроенной функции
map()(поддерживается только один аргумент iterable; для нескольких итерируемых объектов см.starmap()). Блокирует выполнение до получения результата.Этот метод разбивает итерируемый объект на несколько фрагментов и отправляет их в пул процессов как отдельные задачи. (Примерный) размер этих фрагментов можно указать, задав положительное целое число для chunksize.
Обратите внимание, что при очень длинных итерируемых объектах это может привести к высокому потреблению памяти. Для повышения эффективности рассмотрите возможность использовать
imap()илиimap_unordered()с явным указанием параметра chunksize.
-
map_async(func, iterable[, chunksize[, callback[, error_callback]]]) -
Вариант метода
map(), возвращающий объектAsyncResult.Если указан callback, он должен быть вызываемым объектом, принимающим один аргумент. Когда результат будет готов, для него будет вызван callback, если только вызов не завершился ошибкой; в этом случае вместо него вызывается error_callback.
Если указан error_callback, он должен быть вызываемым объектом, принимающим один аргумент. Если целевая функция завершится ошибкой, error_callback будет вызван с экземпляром исключения.
Обратные вызовы должны завершаться немедленно, иначе поток, обрабатывающий результаты, будет заблокирован.
-
imap(func, iterable[, chunksize]) -
Более отложенный вариант
map().Аргумент chunksize такой же, как и в методе
map(). Для очень длинных итерируемых объектов большое значение chunksize может ускорить выполнение задачи значительно по сравнению со значением по умолчанию1.Кроме того, если chunksize равно
1, методnext()итератора, возвращаемого методомimap(), принимает необязательный параметр timeout:next(timeout)вызываетmultiprocessing.TimeoutError, если результат не удаётся вернуть в течение указанного числа секунд timeout.
-
imap_unordered(func, iterable[, chunksize]) -
Аналогичен
imap(), за исключением того, что порядок результатов возвращаемого итератора следует считать произвольным. (Порядок гарантированно будет «правильным» только при использовании одного рабочего процесса.)
-
starmap(func, iterable[, chunksize]) -
Аналогичен
map(), но элементы iterable должны быть итерируемыми объектами, которые распаковываются в аргументы.Таким образом, для iterable из
[(1,2), (3, 4)]результатом будет[func(1,2), func(3,4)].Добавлено в версии 3.3.
-
starmap_async(func, iterable[, chunksize[, callback[, error_callback]]]) -
Сочетание методов
starmap()иmap_async(): перебирает iterable из итерируемых объектов и вызывает func, распаковывая эти объекты в аргументы. Возвращает объект результата.Добавлено в версии 3.3.
-
close() -
Запрещает отправлять в пул новые задачи. После выполнения всех задач рабочие процессы завершат работу.
-
terminate() -
Немедленно останавливает рабочие процессы, не дожидаясь завершения текущих задач. При сборке объекта пула сборщиком мусора немедленно вызывается
terminate().
-
join() -
Ожидает завершения рабочих процессов. Перед вызовом
join()необходимо вызватьclose()илиterminate().
Изменено в версии 3.3: Объекты Pool теперь поддерживают протокол управления контекстом — см. Типы менеджеров контекста.
__enter__()возвращает объект пула, а__exit__()вызываетterminate(). -
-
class multiprocessing.pool.AsyncResult -
Класс результата, возвращаемого методами
Pool.apply_async()иPool.map_async().-
get([timeout]) -
Возвращает результат по мере его получения. Если timeout не равно
Noneи результат не получен в течение указанного числа секунд timeout, вызываетсяmultiprocessing.TimeoutError. Если удалённый вызов вызвал исключение, оно будет повторно вызвано методомget().
-
wait([timeout]) -
Ожидает появления результата или истечения указанного числа секунд timeout.
-
ready() -
Возвращает признак того, что вызов завершён.
-
successful() -
Возвращает признак того, что вызов завершился без исключения. Если результат ещё не готов, вызывает
ValueError.Изменено в версии 3.7: Если результат ещё не готов, вызывается
ValueErrorвместоAssertionError.
-
Следующий пример демонстрирует использование пула:
from multiprocessing import Pool
import time
def f(x):
return x*x
if __name__ == '__main__':
with Pool(processes=4) as pool: # start 4 worker processes
result = pool.apply_async(f, (10,)) # evaluate "f(10)" asynchronously in a single process
print(result.get(timeout=1)) # prints "100" unless your computer is *very* slow
print(pool.map(f, range(10))) # prints "[0, 1, 4,..., 81]"
it = pool.imap(f, range(10))
print(next(it)) # prints "0"
print(next(it)) # prints "1"
print(it.next(timeout=1)) # prints "4" unless your computer is *very* slow
result = pool.apply_async(time.sleep, (10,))
print(result.get(timeout=1)) # raises multiprocessing.TimeoutError
Слушатели и клиенты
Обычно передача сообщений между процессами осуществляется с помощью очередей или объектов Connection, возвращаемых функцией Pipe().
Однако модуль multiprocessing.connection предоставляет дополнительные возможности. По сути, он предлагает высокоуровневый API для обмена сообщениями через сокеты или именованные каналы Windows. Он также поддерживает аутентификацию по дайджесту с помощью модуля hmac и опрос нескольких соединений одновременно.
-
multiprocessing.connection.deliver_challenge(connection, authkey) -
Отправляет другому концу соединения случайно сгенерированное сообщение и ожидает ответа.
Если ответ совпадает с дайджестом сообщения, вычисленным с использованием authkey в качестве ключа, другому концу соединения отправляется приветственное сообщение. В противном случае вызывается
AuthenticationError.
-
multiprocessing.connection.answer_challenge(connection, authkey) -
Получает сообщение, вычисляет его дайджест с использованием authkey в качестве ключа, а затем отправляет дайджест обратно.
Если приветственное сообщение не получено, вызывается
AuthenticationError.
-
multiprocessing.connection.Client(address[, family[, authkey]]) -
Пытается установить соединение со слушателем, использующим адрес address, и возвращает объект
Connection.Тип соединения определяется аргументом family, но обычно его можно не указывать, поскольку тип часто можно определить по формату address. (См. Форматы адресов.)
Если задан authkey и он не равен
None, это должна быть строка байтов, которая будет использоваться как секретный ключ для проверки подлинности на основе HMAC. Если authkey равенNone, проверка подлинности не выполняется. Если проверка не пройдена, вызываетсяAuthenticationError. См. Ключи аутентификации.
-
class multiprocessing.connection.Listener([address[, family[, backlog[, authkey]]]]) -
Обёртка для связанного сокета или именованного канала Windows, который «ожидает» подключения.
address — адрес, используемый связанным сокетом или именованным каналом объекта слушателя.
Примечание
Если используется адрес «0.0.0.0», в Windows он не будет доступной для подключения конечной точкой. Если нужна доступная для подключения конечная точка, следует использовать «127.0.0.1».
family — тип используемого сокета (или именованного канала). Это может быть одна из строк
'AF_INET'(для TCP-сокета),'AF_UNIX'(для доменного сокета Unix) или'AF_PIPE'(для именованного канала Windows). Из них гарантированно доступен только первый вариант. Если family равноNone, семейство определяется по формату address. Если address также равноNone, выбирается значение по умолчанию. Это семейство, которое считается самым быстрым из доступных. См. Форматы адресов. Обратите внимание: если family равно'AF_UNIX', а address равноNone, сокет будет создан во временном приватном каталоге с помощьюtempfile.mkstemp().Если объект слушателя использует сокет, значение backlog (по умолчанию 1) передаётся методу
listen()сокета после его привязки.Если задан authkey и он не равен
None, это должна быть строка байтов, которая будет использоваться как секретный ключ для проверки подлинности на основе HMAC. Если authkey равенNone, проверка подлинности не выполняется. Если проверка не пройдена, вызываетсяAuthenticationError. См. Ключи аутентификации.-
accept() -
Принимает соединение через связанный сокет или именованный канал объекта слушателя и возвращает объект
Connection. Если выполняется проверка подлинности и она не проходит, вызываетсяAuthenticationError.
-
close() -
Закрывает связанный сокет или именованный канал объекта слушателя. Это происходит автоматически при удалении объекта слушателя сборщиком мусора. Тем не менее рекомендуется вызывать этот метод явно.
Объекты слушателей имеют следующие свойства, доступные только для чтения:
-
address -
Адрес, используемый объектом Listener.
-
last_accepted -
Адрес, с которого поступило последнее принятое соединение. Если он недоступен, возвращается
None.
Изменено в версии 3.3: Объекты Listener теперь поддерживают протокол управления контекстом — см. Типы менеджеров контекста.
__enter__()возвращает объект слушателя, а__exit__()вызываетclose(). -
-
multiprocessing.connection.wait(object_list, timeout=None) -
Ожидает готовности одного из объектов в object_list. Возвращает список готовых объектов из object_list. Если timeout — число с плавающей точкой, вызов блокируется не более указанного числа секунд. Если timeout равно
None, блокировка будет длиться неограниченное время. Отрицательное значение тайм-аута эквивалентно нулевому.В POSIX и Windows в object_list могут содержаться:
- доступный для чтения объект
Connection; - подключённый и доступный для чтения объект
socket.socket; или - атрибут
sentinelобъектаProcess.
Объект соединения или сокета готов, если из него можно прочитать данные или если другой конец соединения закрыт.
POSIX:
wait(object_list, timeout)почти эквивалентенselect.select(object_list, [], [], timeout). Разница в том, что еслиselect.select()прерывается сигналом, он может вызватьOSErrorс номером ошибкиEINTR, тогда какwait()этого не сделает.Windows: Элементом object_list должен быть целочисленный дескриптор, допускающий ожидание (согласно определению в документации функции Win32
WaitForMultipleObjects()), либо объект с методомfileno(), возвращающим дескриптор сокета или канала. (Обратите внимание, что дескрипторы каналов и сокетов не допускают ожидание.)Добавлено в версии 3.3.
- доступный для чтения объект
Примеры
Следующий код сервера создаёт слушатель, использующий 'secret password' в качестве ключа аутентификации. Затем он ожидает подключения и отправляет клиенту данные:
from multiprocessing.connection import Listener
from array import array
address = ('localhost', 6000) # family is deduced to be 'AF_INET'
with Listener(address, authkey=b'secret password') as listener:
with listener.accept() as conn:
print('connection accepted from', listener.last_accepted)
conn.send([2.25, None, 'junk', float])
conn.send_bytes(b'hello')
conn.send_bytes(array('i', [42, 1729]))
Следующий код подключается к серверу и получает от него данные:
from multiprocessing.connection import Client
from array import array
address = ('localhost', 6000)
with Client(address, authkey=b'secret password') as conn:
print(conn.recv()) # => [2.25, None, 'junk', float]
print(conn.recv_bytes()) # => 'hello'
arr = array('i', [0, 0, 0, 0, 0])
print(conn.recv_bytes_into(arr)) # => 8
print(arr) # => array('i', [42, 1729, 0, 0, 0])
Следующий код использует wait(), чтобы одновременно ожидать сообщения от нескольких процессов:
from multiprocessing import Process, Pipe, current_process
from multiprocessing.connection import wait
def foo(w):
for i in range(10):
w.send((i, current_process().name))
w.close()
if __name__ == '__main__':
readers = []
for i in range(4):
r, w = Pipe(duplex=False)
readers.append(r)
p = Process(target=foo, args=(w,))
p.start()
# We close the writable end of the pipe now to be sure that
# p is the only process which owns a handle for it. This
# ensures that when p closes its handle for the writable end,
# wait() will promptly report the readable end as being ready.
w.close()
while readers:
for r in wait(readers):
try:
msg = r.recv()
except EOFError:
readers.remove(r)
else:
print(msg)
Форматы адресов
- Адрес
'AF_INET'— это кортеж вида(hostname, port), где hostname — строка, а port — целое число. - Адрес
'AF_UNIX'— это строка, представляющая имя файла в файловой системе. - Адрес
'AF_PIPE'— это строка видаr'\\.\pipe\PipeName'. Чтобы подключиться с помощьюClient()к именованному каналу на удалённом компьютере с именем ServerName, следует использовать адрес видаr'\\ServerName\pipe\PipeName'.
Обратите внимание, что любая строка, начинающаяся с двух обратных косых черт, по умолчанию считается адресом 'AF_PIPE', а не адресом 'AF_UNIX'.
Ключи аутентификации
При использовании Connection.recv полученные данные автоматически распаковываются с помощью pickle. К сожалению, распаковка данных из ненадёжного источника представляет угрозу безопасности. Поэтому Listener и Client() используют модуль hmac для аутентификации по дайджесту.
Ключ аутентификации — это строка байтов, которую можно считать паролем: после установления соединения обе стороны требуют доказательства того, что другая сторона знает ключ аутентификации. (Доказательство того, что обе стороны используют один и тот же ключ, не предполагает отправку ключа по соединению.)
Если запрошена аутентификация, но ключ не указан, используется возвращаемое значение current_process().authkey (см. Process). Это значение автоматически наследуется любым объектом Process, созданным текущим процессом. Это означает, что (по умолчанию) все процессы многопроцессной программы используют один ключ аутентификации, который можно применять при установлении соединений между ними.
Подходящие ключи аутентификации также можно сгенерировать с помощью os.urandom().
Журналирование
Доступна некоторая поддержка журналирования. Однако обратите внимание, что пакет logging не использует блокировки, общие для процессов, поэтому сообщения разных процессов могут перемешиваться (в зависимости от типа обработчика).
-
multiprocessing.get_logger() -
Возвращает средство ведения журнала, используемое
multiprocessing. При необходимости будет создано новое.При первом создании уровень средства ведения журнала равен
logging.NOTSET, а обработчик по умолчанию отсутствует. По умолчанию сообщения, отправленные этому средству ведения журнала, не передаются корневому средству ведения журнала.Обратите внимание, что в Windows дочерние процессы наследуют только уровень средства ведения журнала родительского процесса — любые другие настройки средства ведения журнала не наследуются.
-
multiprocessing.log_to_stderr(level=None) -
Эта функция вызывает
get_logger(), но, помимо возврата средства ведения журнала, созданного get_logger, добавляет обработчик, который отправляет вывод вsys.stderr, используя формат'[%(levelname)s/%(processName)s] %(message)s'. Вы можете изменитьlevelnameсредства ведения журнала, передав аргументlevel.
Ниже приведён пример сеанса с включённым журналированием:
>>> import multiprocessing, logging
>>> logger = multiprocessing.log_to_stderr()
>>> logger.setLevel(logging.INFO)
>>> logger.warning('doomed')
[WARNING/MainProcess] doomed
>>> m = multiprocessing.Manager()
[INFO/SyncManager-...] child process calling self.run()
[INFO/SyncManager-...] created temp directory /.../pymp-...
[INFO/SyncManager-...] manager serving at '/.../listener-...'
>>> del m
[INFO/MainProcess] sending shutdown message to manager
[INFO/SyncManager-...] manager exiting with exitcode 0
Полную таблицу уровней журналирования см. в модуле logging.
Модуль multiprocessing.dummy
multiprocessing.dummy воспроизводит API модуля multiprocessing, но представляет собой лишь оболочку над модулем threading.
В частности, функция Pool, предоставляемая multiprocessing.dummy, возвращает экземпляр ThreadPool — подкласса Pool, поддерживающего те же вызовы методов, но использующего пул рабочих потоков, а не рабочих процессов.
-
class multiprocessing.pool.ThreadPool([processes[, initializer[, initargs]]]) -
Объект пула потоков, управляющий пулом рабочих потоков, которым можно отправлять задания. Экземпляры
ThreadPoolполностью совместимы по интерфейсу с экземплярамиPool, а их ресурсами также необходимо надлежащим образом управлять: использовать пул как менеджер контекста либо вручную вызыватьclose()иterminate().processes — количество используемых рабочих потоков. Если processes равно
None, используется число, возвращаемоеos.process_cpu_count().Если initializer не равно
None, каждый рабочий процесс вызоветinitializer(*initargs)при запуске.В отличие от
Pool, параметры maxtasksperchild и context указывать нельзя.Примечание
ThreadPoolимеет тот же интерфейс, что иPool, разработанный для пула процессов ещё до появления модуляconcurrent.futures. Поэтому он наследует некоторые операции, не имеющие смысла для пула на основе потоков, и использует собственный тип для представления состояния асинхронных заданий —AsyncResult, который не распознаётся другими библиотеками.Обычно предпочтительнее использовать
concurrent.futures.ThreadPoolExecutor: его более простой интерфейс изначально разработан для потоков, а возвращаемые им экземплярыconcurrent.futures.Futureсовместимы со многими другими библиотеками, включаяasyncio.
Рекомендации по программированию
При использовании multiprocessing следует придерживаться ряда рекомендаций и идиом.
Все методы запуска
Следующие рекомендации применимы ко всем методам запуска.
Избегайте общего состояния
По возможности старайтесь не передавать между процессами большие объёмы данных.
Для связи между процессами, вероятно, лучше использовать очереди или каналы, а не низкоуровневые примитивы синхронизации.
Возможность сериализации с помощью pickle
Убедитесь, что аргументы методов прокси можно сериализовать с помощью pickle.
Потокобезопасность прокси
Не используйте объект прокси из нескольких потоков, если не защитили его блокировкой.
(Проблем не возникает, когда разные процессы используют один и тот же прокси.)
Присоединение зомби-процессов
В POSIX завершившийся процесс, к которому не было выполнено присоединение, становится зомби. Таких процессов не должно быть много, поскольку при каждом запуске нового процесса (или вызове active_children()) выполняется присоединение всех завершённых процессов, к которым оно ещё не было выполнено. Кроме того, вызов Process.is_alive для завершившегося процесса также выполняет присоединение. Тем не менее рекомендуется явно выполнять присоединение всех запущенных процессов.
Наследуйте, а не сериализуйте и десериализуйте
При использовании методов запуска spawn или forkserver многие типы из multiprocessing должны поддерживать сериализацию с помощью pickle, чтобы их могли использовать дочерние процессы. Однако обычно не следует отправлять общие объекты другим процессам через каналы или очереди. Вместо этого организуйте программу так, чтобы процесс, которому требуется доступ к общему ресурсу, созданному в другом месте, мог унаследовать его от процесса-предка.
Избегайте завершения процессов
Использование метода Process.terminate для остановки процесса может привести к тому, что любые используемые им общие ресурсы (например, блокировки, семафоры, каналы и очереди) окажутся повреждены или недоступны для других процессов.
Поэтому, вероятно, лучше использовать Process.terminate только для процессов, которые никогда не используют общие ресурсы.
Присоединение процессов, использующих очереди
Помните, что процесс, добавивший элементы в очередь, будет ожидать перед завершением, пока все буферизованные элементы не будут переданы «потоком-поставщиком» в базовый канал. (Чтобы избежать такого поведения, дочерний процесс может вызвать метод очереди Queue.cancel_join_thread.)
Это означает, что при использовании очереди необходимо убедиться, что все добавленные в неё элементы будут извлечены до присоединения процесса. Иначе нельзя быть уверенным, что процессы, добавившие элементы в очередь, завершатся. Помните также, что присоединение процессов, не являющихся демонами, выполняется автоматически.
Следующий пример приведёт к взаимной блокировке:
from multiprocessing import Process, Queue
def f(q):
q.put('X' * 1000000)
if __name__ == '__main__':
queue = Queue()
p = Process(target=f, args=(queue,))
p.start()
p.join() # this deadlocks
obj = queue.get()
Чтобы исправить это, достаточно поменять местами две последние строки (или просто удалить строку p.join()).
Явно передавайте ресурсы дочерним процессам
В POSIX при использовании метода запуска fork дочерний процесс может получить доступ к общему ресурсу, созданному в родительском процессе, через глобальную переменную. Однако лучше передавать объект в качестве аргумента конструктору дочернего процесса.
Это не только (потенциально) обеспечивает совместимость кода с Windows и другими методами запуска, но и гарантирует, что объект не будет удалён сборщиком мусора в родительском процессе, пока дочерний процесс остаётся активным. Это может быть важно, если при удалении объекта сборщиком мусора в родительском процессе освобождается какой-либо ресурс.
Например,
from multiprocessing import Process, Lock
def f():
... do something using "lock" ...
if __name__ == '__main__':
lock = Lock()
for i in range(10):
Process(target=f).start()
следует переписать так:
from multiprocessing import Process, Lock
def f(l):
... do something using "l" ...
if __name__ == '__main__':
lock = Lock()
for i in range(10):
Process(target=f, args=(lock,)).start()
Будьте осторожны, заменяя sys.stdin «файлоподобным объектом»
Изначально multiprocessing безусловно вызывал:
os.close(sys.stdin.fileno())
в методе multiprocessing.Process._bootstrap() — это приводило к проблемам при вложенных процессах. Теперь это изменено на:
sys.stdin.close() sys.stdin = open(os.open(os.devnull, os.O_RDONLY), closefd=False)
Это решает основную проблему взаимного вмешательства процессов, приводившую к ошибке неверного файлового дескриптора, но создаёт потенциальную опасность для приложений, заменяющих sys.stdin() «файлоподобным объектом» с буферизацией вывода. Если несколько процессов вызовут close() для такого файлоподобного объекта, одни и те же данные могут быть сброшены в объект несколько раз, что приведёт к повреждению данных.
Если вы пишете файлоподобный объект и реализуете собственное кэширование, можно обеспечить его безопасность при создании процессов методом fork: сохраняйте PID при каждом добавлении данных в кэш и очищайте кэш при изменении PID. Например:
@property
def cache(self):
pid = os.getpid()
if pid != self._pid:
self._pid = pid
self._cache = []
return self._cache
Дополнительную информацию см. в bpo-5155, bpo-5313 и bpo-5331.
Методы запуска spawn и forkserver
Для этих методов действуют несколько дополнительных ограничений, которые не применяются к методу запуска fork.
Дополнительные требования к сериализации с помощью pickle
Убедитесь, что все аргументы Process можно сериализовать с помощью pickle. Кроме того, если вы создаёте подкласс Process.__init__, необходимо убедиться, что его экземпляры можно сериализовать с помощью pickle при вызове метода Process.start.
Глобальные переменные
Помните: если код, выполняемый в дочернем процессе, пытается получить доступ к глобальной переменной, её значение (если оно есть) может отличаться от значения в родительском процессе на момент вызова Process.start.
Однако глобальные переменные, являющиеся лишь константами уровня модуля, проблем не вызывают.
Безопасный импорт главного модуля
Убедитесь, что главный модуль можно безопасно импортировать в новом интерпретаторе Python, не вызывая нежелательных побочных эффектов (например, запуска нового процесса).
Например, при использовании метода запуска spawn или forkserver выполнение следующего модуля завершится ошибкой RuntimeError:
from multiprocessing import Process
def foo():
print('hello')
p = Process(target=foo)
p.start()
Вместо этого следует защитить «точку входа» программы с помощью if
__name__ == '__main__': следующим образом:
from multiprocessing import Process, freeze_support, set_start_method
def foo():
print('hello')
if __name__ == '__main__':
freeze_support()
set_start_method('spawn')
p = Process(target=foo)
p.start()
(Строку freeze_support() можно опустить, если программа будет запускаться обычным образом, а не в замороженном виде.)
Это позволяет только что запущенному интерпретатору Python безопасно импортировать модуль, а затем выполнить функцию foo() этого модуля.
Аналогичные ограничения действуют, если пул или менеджер создаётся в главном модуле.
Примеры
Пример создания и использования настраиваемых менеджеров и прокси:
from multiprocessing import freeze_support
from multiprocessing.managers import BaseManager, BaseProxy
import operator
##
class Foo:
def f(self):
print('you called Foo.f()')
def g(self):
print('you called Foo.g()')
def _h(self):
print('you called Foo._h()')
# A simple generator function
def baz():
for i in range(10):
yield i*i
# Proxy type for generator objects
class GeneratorProxy(BaseProxy):
_exposed_ = ['__next__']
def __iter__(self):
return self
def __next__(self):
return self._callmethod('__next__')
# Function to return the operator module
def get_operator_module():
return operator
##
class MyManager(BaseManager):
pass
# register the Foo class; make `f()` and `g()` accessible via proxy
MyManager.register('Foo1', Foo)
# register the Foo class; make `g()` and `_h()` accessible via proxy
MyManager.register('Foo2', Foo, exposed=('g', '_h'))
# register the generator function baz; use `GeneratorProxy` to make proxies
MyManager.register('baz', baz, proxytype=GeneratorProxy)
# register get_operator_module(); make public functions accessible via proxy
MyManager.register('operator', get_operator_module)
##
def test():
manager = MyManager()
manager.start()
print('-' * 20)
f1 = manager.Foo1()
f1.f()
f1.g()
assert not hasattr(f1, '_h')
assert sorted(f1._exposed_) == sorted(['f', 'g'])
print('-' * 20)
f2 = manager.Foo2()
f2.g()
f2._h()
assert not hasattr(f2, 'f')
assert sorted(f2._exposed_) == sorted(['g', '_h'])
print('-' * 20)
it = manager.baz()
for i in it:
print('<%d>' % i, end=' ')
print()
print('-' * 20)
op = manager.operator()
print('op.add(23, 45) =', op.add(23, 45))
print('op.pow(2, 94) =', op.pow(2, 94))
print('op._exposed_ =', op._exposed_)
##
if __name__ == '__main__':
freeze_support()
test()
Использование Pool:
import multiprocessing
import time
import random
import sys
#
# Functions used by test code
#
def calculate(func, args):
result = func(*args)
return '%s says that %s%s = %s' % (
multiprocessing.current_process().name,
func.__name__, args, result
)
def calculatestar(args):
return calculate(*args)
def mul(a, b):
time.sleep(0.5 * random.random())
return a * b
def plus(a, b):
time.sleep(0.5 * random.random())
return a + b
def f(x):
return 1.0 / (x - 5.0)
def pow3(x):
return x ** 3
def noop(x):
pass
#
# Test code
#
def test():
PROCESSES = 4
print('Creating pool with %d processes\n' % PROCESSES)
with multiprocessing.Pool(PROCESSES) as pool:
#
# Tests
#
TASKS = [(mul, (i, 7)) for i in range(10)] + \
[(plus, (i, 8)) for i in range(10)]
results = [pool.apply_async(calculate, t) for t in TASKS]
imap_it = pool.imap(calculatestar, TASKS)
imap_unordered_it = pool.imap_unordered(calculatestar, TASKS)
print('Ordered results using pool.apply_async():')
for r in results:
print('\t', r.get())
print()
print('Ordered results using pool.imap():')
for x in imap_it:
print('\t', x)
print()
print('Unordered results using pool.imap_unordered():')
for x in imap_unordered_it:
print('\t', x)
print()
print('Ordered results using pool.map() --- will block till complete:')
for x in pool.map(calculatestar, TASKS):
print('\t', x)
print()
#
# Test error handling
#
print('Testing error handling:')
try:
print(pool.apply(f, (5,)))
except ZeroDivisionError:
print('\tGot ZeroDivisionError as expected from pool.apply()')
else:
raise AssertionError('expected ZeroDivisionError')
try:
print(pool.map(f, list(range(10))))
except ZeroDivisionError:
print('\tGot ZeroDivisionError as expected from pool.map()')
else:
raise AssertionError('expected ZeroDivisionError')
try:
print(list(pool.imap(f, list(range(10)))))
except ZeroDivisionError:
print('\tGot ZeroDivisionError as expected from list(pool.imap())')
else:
raise AssertionError('expected ZeroDivisionError')
it = pool.imap(f, list(range(10)))
for i in range(10):
try:
x = next(it)
except ZeroDivisionError:
if i == 5:
pass
except StopIteration:
break
else:
if i == 5:
raise AssertionError('expected ZeroDivisionError')
assert i == 9
print('\tGot ZeroDivisionError as expected from IMapIterator.next()')
print()
#
# Testing timeouts
#
print('Testing ApplyResult.get() with timeout:', end=' ')
res = pool.apply_async(calculate, TASKS[0])
while 1:
sys.stdout.flush()
try:
sys.stdout.write('\n\t%s' % res.get(0.02))
break
except multiprocessing.TimeoutError:
sys.stdout.write('.')
print()
print()
print('Testing IMapIterator.next() with timeout:', end=' ')
it = pool.imap(calculatestar, TASKS)
while 1:
sys.stdout.flush()
try:
sys.stdout.write('\n\t%s' % it.next(0.02))
except StopIteration:
break
except multiprocessing.TimeoutError:
sys.stdout.write('.')
print()
print()
if __name__ == '__main__':
multiprocessing.freeze_support()
test()
Пример использования очередей для передачи заданий группе рабочих процессов и сбора результатов:
import time
import random
from multiprocessing import Process, Queue, current_process, freeze_support
#
# Function run by worker processes
#
def worker(input, output):
for func, args in iter(input.get, 'STOP'):
result = calculate(func, args)
output.put(result)
#
# Function used to calculate result
#
def calculate(func, args):
result = func(*args)
return '%s says that %s%s = %s' % \
(current_process().name, func.__name__, args, result)
#
# Functions referenced by tasks
#
def mul(a, b):
time.sleep(0.5*random.random())
return a * b
def plus(a, b):
time.sleep(0.5*random.random())
return a + b
#
#
#
def test():
NUMBER_OF_PROCESSES = 4
TASKS1 = [(mul, (i, 7)) for i in range(20)]
TASKS2 = [(plus, (i, 8)) for i in range(10)]
# Create queues
task_queue = Queue()
done_queue = Queue()
# Submit tasks
for task in TASKS1:
task_queue.put(task)
# Start worker processes
for i in range(NUMBER_OF_PROCESSES):
Process(target=worker, args=(task_queue, done_queue)).start()
# Get and print results
print('Unordered results:')
for i in range(len(TASKS1)):
print('\t', done_queue.get())
# Add more tasks using `put()`
for task in TASKS2:
task_queue.put(task)
# Get and print some more results
for i in range(len(TASKS2)):
print('\t', done_queue.get())
# Tell child processes to stop
for i in range(NUMBER_OF_PROCESSES):
task_queue.put('STOP')
if __name__ == '__main__':
freeze_support()
test()
© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/multiprocessing.html