Документация Python неофициальный перевод

multiprocessing.shared_memory – Разделяемая память для прямого доступа между процессамиmultiprocessing.shared_memory – Shared memory for direct access across processes

Исходный код: Lib/multiprocessing/shared_memory.py

Новое в версии 3.8.


Этот модуль предоставляет класс SharedMemory для выделения и управления разделяемой памятью, к которой могут обращаться один или несколько процессов на многоядерной или симметричной мультипроцессорной (SMP) машине. Для упрощения управления жизненным циклом разделяемой памяти, особенно между разными процессами, в модуле multiprocessing.managers также предоставляется подкласс BaseManagerSharedMemoryManager.

В этом модуле разделяемая память означает блоки разделяемой памяти «в стиле System V» (хотя реализация не обязательно буквально такая) и не относится к «распределённой разделяемой памяти». Такой стиль разделяемой памяти позволяет разным процессам читать и писать в общую (или разделяемую) область оперативной памяти. Обычно процессы ограничены доступом только к своему собственному адресному пространству, но разделяемая память позволяет обмениваться данными между процессами, избегая необходимости отправлять сообщения между процессами, содержащие эти данные. Прямое разделение данных через память может дать значительный выигрыш в производительности по сравнению с передачей данных через диск, сокет или другие механизмы, требующие сериализации/десериализации и копирования данных.

class multiprocessing.shared_memory.SharedMemory(name=None, create=False, size=0)

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

Будучи ресурсом для обмена данными между процессами, блоки разделяемой памяти могут пережить породивший их процесс. Когда процесс больше не нуждается в доступе к блоку разделяемой памяти, который может по-прежнему требоваться другим процессам, следует вызвать метод close(). Когда блок разделяемой памяти больше не нужен ни одному процессу, для надлежащей очистки следует вызвать метод unlink().

name – это уникальное имя для запрашиваемой разделяемой памяти, задаваемое строкой. При создании нового блока разделяемой памяти, если в качестве имени указано None (значение по умолчанию), будет сгенерировано новое имя.

create управляет тем, создаётся ли новый блок разделяемой памяти (True) или подключается существующий блок разделяемой памяти (False).

size задаёт запрашиваемое количество байт при создании нового блока разделяемой памяти. Поскольку некоторые платформы выделяют память блоками в соответствии с размером страницы памяти, фактический размер блока разделяемой памяти может быть больше или равен запрошенному. При подключении к существующему блоку разделяемой памяти параметр size игнорируется.

close()

Закрывает доступ к разделяемой памяти из данного экземпляра. Для обеспечения правильной очистки ресурсов каждый экземпляр должен вызвать close(), когда он больше не нужен. Обратите внимание: вызов close() не приводит к уничтожению самого блока разделяемой памяти.

Запрашивает уничтожение нижележащего блока разделяемой памяти. Для обеспечения правильной очистки ресурсов unlink() должен быть вызван один раз (и только один раз) во всех процессах, которым нужен этот блок разделяемой памяти. После запроса на уничтожение блок разделяемой памяти может быть уничтожен немедленно или не сразу, и это поведение может различаться на разных платформах. Попытки доступа к данным внутри блока разделяемой памяти после вызова unlink() могут привести к ошибкам доступа к памяти. Примечание: последний процесс, освобождающий блок разделяемой памяти, может вызывать unlink() и close() в любом порядке.

buf

Представление памяти (memoryview) содержимого блока разделяемой памяти.

name

Доступ только для чтения к уникальному имени блока разделяемой памяти.

size

Доступ только для чтения к размеру блока разделяемой памяти в байтах.

В следующем примере демонстрируется низкоуровневое использование экземпляров SharedMemory:

>>> from multiprocessing import shared_memory
>>> shm_a = shared_memory.SharedMemory(create=True, size=10)
>>> type(shm_a.buf)
<class 'memoryview'>
>>> buffer = shm_a.buf
>>> len(buffer)
10
>>> buffer[:4] = bytearray([22, 33, 44, 55])  # Изменить несколько за раз
>>> buffer[4] = 100                           # Изменить один байт за раз
>>> # Подключиться к существующему блоку разделяемой памяти
>>> shm_b = shared_memory.SharedMemory(shm_a.name)
>>> import array
>>> array.array('b', shm_b.buf[:5])  # Скопировать данные в новый array.array
array('b', [22, 33, 44, 55, 100])
>>> shm_b.buf[:5] = b'howdy'  # Изменить через shm_b, используя bytes
>>> bytes(shm_a.buf[:5])      # Доступ через shm_a
b'howdy'
>>> shm_b.close()   # Закрыть каждый экземпляр SharedMemory
>>> shm_a.close()
>>> shm_a.unlink()  # Вызвать unlink только один раз, чтобы освободить разделяемую память

В следующем примере демонстрируется практическое применение класса SharedMemory с массивами NumPy – доступ к одному и тому же numpy.ndarray из двух разных оболочек Python:

>>> # В первой интерактивной оболочке Python
>>> import numpy as np
>>> a = np.array([1, 1, 2, 3, 5, 8])  # Начать с существующего массива NumPy
>>> from multiprocessing import shared_memory
>>> shm = shared_memory.SharedMemory(create=True, size=a.nbytes)
>>> # Теперь создать массив NumPy, поддерживаемый разделяемой памятью
>>> b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf)
>>> b[:] = a[:]  # Скопировать исходные данные в разделяемую память
>>> b
array([1, 1, 2, 3, 5, 8])
>>> type(b)
<class 'numpy.ndarray'>
>>> type(a)
<class 'numpy.ndarray'>
>>> shm.name  # Мы не указали имя, поэтому оно было выбрано автоматически
'psm_21467_46075'

>>> # В той же оболочке или в новой оболочке Python на той же машине
>>> import numpy as np
>>> from multiprocessing import shared_memory
>>> # Подключиться к существующему блоку разделяемой памяти
>>> existing_shm = shared_memory.SharedMemory(name='psm_21467_46075')
>>> # Обратите внимание, что a.shape равно (6,), а a.dtype равно np.int64 в этом примере
>>> c = np.ndarray((6,), dtype=np.int64, buffer=existing_shm.buf)
>>> c
array([1, 1, 2, 3, 5, 8])
>>> c[-1] = 888
>>> c
array([  1,   1,   2,   3,   5, 888])

>>> # Вернувшись в первую интерактивную оболочку Python, b отражает это изменение
>>> b
array([  1,   1,   2,   3,   5, 888])

>>> # Очистка из второй оболочки Python
>>> del c  # Необязательно; просто подчёркивается, что массив больше не используется
>>> existing_shm.close()

>>> # Очистка из первой оболочки Python
>>> del b  # Необязательно; просто подчёркивается, что массив больше не используется
>>> shm.close()
>>> shm.unlink()  # Освободить и удалить блок разделяемой памяти в самом конце
class multiprocessing.managers.SharedMemoryManager([address[, authkey]])

Подкласс BaseManager, который можно использовать для управления блоками разделяемой памяти между процессами.

Вызов start() у экземпляра SharedMemoryManager запускает новый процесс. Единственная цель этого нового процесса – управлять жизненным циклом всех блоков разделяемой памяти, созданных через него. Чтобы запустить освобождение всех блоков разделяемой памяти, управляемых этим процессом, вызовите shutdown() у экземпляра. Это вызовет SharedMemory.unlink() для всех объектов SharedMemory, управляемых этим процессом, а затем остановит сам процесс. Создавая экземпляры SharedMemory через SharedMemoryManager, мы избегаем необходимости вручную отслеживать и инициировать освобождение ресурсов разделяемой памяти.

Этот класс предоставляет методы для создания и возврата экземпляров SharedMemory, а также для создания объекта, подобного списку (ShareableList), основанного на разделяемой памяти.

Обратитесь к multiprocessing.managers.BaseManager за описанием унаследованных необязательных входных аргументов address и authkey, а также за тем, как их можно использовать для подключения к существующей службе SharedMemoryManager из других процессов.

SharedMemory(size)

Создаёт и возвращает новый объект SharedMemory с указанным size в байтах.

ShareableList(sequence)

Создаёт и возвращает новый объект ShareableList, инициализированный значениями из входного sequence.

Следующий пример демонстрирует основные механизмы SharedMemoryManager:

>>> from multiprocessing.managers import SharedMemoryManager
>>> smm = SharedMemoryManager()
>>> smm.start()  # Запуск процесса, управляющего блоками разделяемой памяти
>>> sl = smm.ShareableList(range(4))
>>> sl
ShareableList([0, 1, 2, 3], name='psm_6572_7512')
>>> raw_shm = smm.SharedMemory(size=128)
>>> another_sl = smm.ShareableList('alpha')
>>> another_sl
ShareableList(['a', 'l', 'p', 'h', 'a'], name='psm_6572_12221')
>>> smm.shutdown()  # Вызывает unlink() для sl, raw_shm и another_sl

Следующий пример демонстрирует потенциально более удобный шаблон использования объектов SharedMemoryManager через оператор with, чтобы гарантировать освобождение всех блоков разделяемой памяти после того, как они больше не нужны:

>>> with SharedMemoryManager() as smm:
...     sl = smm.ShareableList(range(2000))
...     # Разделение работы между двумя процессами, сохранение частичных результатов в sl
...     p1 = Process(target=do_work, args=(sl, 0, 1000))
...     p2 = Process(target=do_work, args=(sl, 1000, 2000))
...     p1.start()
...     p2.start()  # multiprocessing.Pool может быть эффективнее
...     p1.join()
...     p2.join()   # Ожидание завершения всей работы в обоих процессах
...     total_result = sum(sl)  # Объединение частичных результатов из sl

При использовании SharedMemoryManager в операторе with все блоки разделяемой памяти, созданные с помощью этого менеджера, освобождаются при завершении выполнения блока кода оператора with.

class multiprocessing.shared_memory.ShareableList(sequence=None, *, name=None)

Предоставляет изменяемый объект, подобный списку, где все хранящиеся значения хранятся в блоке разделяемой памяти. Это ограничивает сохраняемые значения только встроенными типами данных int, float, bool, str (менее 10 МБ каждый), bytes (менее 10 МБ каждый) и None. Он также существенно отличается от встроенного типа list тем, что эти списки не могут изменять свою общую длину (т.е. нет append, insert и т.д.) и не поддерживают динамическое создание новых экземпляров ShareableList через срезы.

sequence используется для заполнения нового ShareableList значениями. Установите в None, чтобы вместо этого подключиться к уже существующему ShareableList по его уникальному имени разделяемой памяти.

name – это уникальное имя для запрашиваемой разделяемой памяти, как описано в определении SharedMemory. При подключении к существующему ShareableList укажите уникальное имя его блока разделяемой памяти, оставив sequence равным None.

count(value)

Возвращает количество вхождений value.

index(value)

Возвращает индекс первого вхождения value. Возбуждает ValueError, если value отсутствует.

format

Атрибут только для чтения, содержащий формат упаковки struct, используемый всеми текущими хранимыми значениями.

shm

Экземпляр SharedMemory, в котором хранятся значения.

Следующий пример демонстрирует базовое использование экземпляра ShareableList:

>>> from multiprocessing import shared_memory
>>> a = shared_memory.ShareableList(['howdy', b'HoWdY', -273.154, 100, None, True, 42])
>>> [ type(entry) for entry in a ]
[<class 'str'>, <class 'bytes'>, <class 'float'>, <class 'int'>, <class 'NoneType'>, <class 'bool'>, <class 'int'>]
>>> a[2]
-273.154
>>> a[2] = -78.5
>>> a[2]
-78.5
>>> a[2] = 'dry ice'  # Изменение типов данных также поддерживается
>>> a[2]
'dry ice'
>>> a[2] = 'larger than previously allocated storage space'
Traceback (most recent call last):
  ...
ValueError: exceeds available storage for existing str
>>> a[2]
'dry ice'
>>> len(a)
7
>>> a.index(42)
6
>>> a.count(b'howdy')
0
>>> a.count(b'HoWdY')
1
>>> a.shm.close()
>>> a.shm.unlink()
>>> del a  # Использование ShareableList после вызова unlink() не поддерживается

Следующий пример показывает, как один, два или много процессов могут получить доступ к одному и тому же ShareableList, указав имя лежащего в его основе блока разделяемой памяти:

>>> b = shared_memory.ShareableList(range(5))         # В первом процессе
>>> c = shared_memory.ShareableList(name=b.shm.name)  # Во втором процессе
>>> c
ShareableList([0, 1, 2, 3, 4], name='...')
>>> c[-1] = -999
>>> b[-1]
-999
>>> b.shm.close()
>>> c.shm.close()
>>> c.shm.unlink()

Следующий пример демонстрирует, что объекты ShareableList (и лежащие в их основе SharedMemory) могут быть сериализованы и десериализованы при необходимости. Обратите внимание, что это всё равно будет тот же самый общий объект. Это происходит потому, что десериализованный объект имеет то же уникальное имя и просто подключается к существующему объекту с тем же именем (если объект ещё жив):

>>> import pickle
>>> from multiprocessing import shared_memory
>>> sl = shared_memory.ShareableList(range(10))
>>> list(sl)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> deserialized_sl = pickle.loads(pickle.dumps(sl))
>>> list(deserialized_sl)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sl[0] = -1
>>> deserialized_sl[1] = -2
>>> list(sl)
[-1, -2, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(deserialized_sl)
[-1, -2, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sl.shm.close()
>>> sl.shm.unlink()