В примере 5.31 используется разделяемая память, которая служит вводом и выводом порожденных процессов. Чтобы обеспечить переносимость этого приема, необходимо создать объекты с помощью пакета и передать их конструктору Process. Последний тест в этом примере («loop 4») представляет, пожалуй, наиболее типичный случай использования разделяемой памяти — распределение заданий по нескольким параллельно выполняющимся процессам.
Пример 5.31. PP4E\System\Processes\multi3.py
Реализует взаимодействие с помощью объектов разделяемой памяти из пакета.
В Windows передаваемые объекты используются совместно, а глобальные объекты
— нет. Последняя проверка здесь отражает типичный случай использования: распределение заданий между процессами.
import os
from multiprocessing import Process, Value, Array
procs = 3 # глобальные переменные, отдельные для каждого процесса,
count = 0 # не являются совместно используемыми
def showdata(label, val, arr):
выводит значения данных в этом процессе
msg = ‘%-12s: pid:%4s, global:%s, value:%s, array:%s’
print(msg % (label, os.getpid(), count, val.value, list(arr)))
def updater(val, arr):
обменивается данными через разделяемую память
global count
count += 1 # глобальный счетчик недоступен другим процессам
val.value += 1 # а передаваемый в объекте — доступен
for i in range(3): arr[i] += 1
if __name__ == ‘__main__’:
scalar = Value(‘i’, 0) # разделяемая память: предусматривает
# синхронизацию процессов/потоков
vector = Array(‘d’, procs) # коды типов из ctypes: int, double
# вывести начальные значения в родительском процессе
showdata(‘parent start’, scalar, vector)
# породить дочерний процесс, передать данные в разделяемой памяти
p = Process(target=showdata, args=(‘child ‘, scalar, vector))
p.start(); p.join()
# изменить значения в родителе и передать через разделяемую память,
# ждать завершения каждого потомка
# все потомки видят изменения, выполненные в родительском процессе и
# переданные в виде аргументов (но не в глобальной памяти)
print(‘\nloop1 (updates in parent, serial children)…’)
for i in range(procs):
count += 1
scalar.value += 1
vector[i] += 1
p = Process(target=showdata, args=((‘process %s’ % i), scalar, vector))
p.start(); p.join()
# то же самое, но потомки запускаются параллельно
# все они видят результат последней итерации, потому что они хранятся
# в совместно используемых объектах
print(‘\nloop2 (updates in parent, parallel children)…’)
ps = []
for i in range(procs):
count += 1
scalar.value += 1
vector[i] += 1
p = Process(target=showdata, args=((‘process %s’ % i),
scalar, vector))
p.start()
ps.append(p)
for p in ps: p.join()
# объекты в разделяемой памяти изменяются потомками,
# ждать завершения каждого из них
print(‘\nloop3 (updates in serial children)…’) for i in range(procs):
p = Process(target=updater, args=(scalar, vector)) p.start() p.join() showdata(‘parent temp’, scalar, vector)
# то же самое, но потомки запускаются параллельно
ps = []
print(‘\nloop4 (updates in parallel children)…’) for i in range(procs):
p = Process(target=updater, args=(scalar, vector)) p.start() ps.append(p) for p in ps: p.join()
# глобальная переменная count=6 доступна только родителю
# выведет последние результаты # scalar=12: +6 в родителе, +6 в 6 потомках showdata(‘parent end’, scalar, vector) # array[i]=8:
# +2 в родителе, +6 в 6 потомках
Ниже приводится вывод этого сценария, запущенного в Windows. Проследите, как выполняется этот программный код. Обратите внимание, что глобальная переменная недоступна дочерним процессам для совместного использования в Windows, а переданные им объекты Value и Array — доступны. Последняя строка в этом выводе отражает изменения, выполненные в разделяемой памяти родительским и дочерними процессами, — все элементы в массиве имеют значение 8.0, потому что все они дважды увеличивались в родительском процессе и по одному разу — в каждом из шести дочерних процессов. Скалярное значение также отражает изменения, выполненные в родительском и в дочерних процессах, но, в отличие от потоков выполнения, в Windows глобальные переменные доступны только вмещающему их процессу:
C:\…\PP4E\System\Processes> multi3.py
parent start: pid:6204, global:0, value:0, array:[0.0, 0.0, 0.0]
child : pid:9660, global:0, value:0, array:[0.0, 0.0, 0.0]
loop1 (updates in parent, serial children)…
process 0 : pid:3900, global:0, value:1, array:[1.0, 0.0, 0.0]
process 1 : pid:5072, global:0, value:2, array:[1.0, 1.0, 0.0]
process 2 : pid:9472, global:0, value:3, array:[1.0, 1.0, 1.0]
loop2 (updates in parent, parallel children)…
process 1 : pid:9468, global:0, value:6, array:[2.0, 2.0, 2.0]
process 2 : pid:9036, global:0, value:6, array:[2.0, 2.0, 2.0]
process 0 : pid:9548, global:0, value:6, array:[2.0, 2.0, 2.0]
loop3 (updates in serial children)…
parent temp : pid:6204, global:6, value:9, array:[5.0, 5.0, 5.0]
loop4 (updates in parallel children)…
parent end : pid:6204, global:6, value:12, array:[8.0, 8.0, 8.0]
А теперь представьте, что в последнем тесте используется намного больший массив и запускается большее количество дочерних процессов, — тогда вы начнете понимать, какие возможности предоставляет этот пакет для реализации параллельной обработки данных.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011