multiprocessing.shared_memory — Общая память для прямого доступа между процессами

Источник: Lib/multiprocessing/shared_memory.py

Added in version 3.8.


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

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

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

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

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

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

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

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

  • track (bool) – Если True, зарегистрируйте блок разделяемой памяти в процессе отслеживания ресурсов на платформах, где ОС не делает этого автоматически. Процесс отслеживания ресурсов обеспечивает надлежащую очистку разделяемой памяти, даже если все остальные процессы, имеющие доступ к этой памяти, выходят из нее, не сделав этого. Процессы Python, созданные от общего предка с помощью средств multiprocessing, используют один процесс-трекер ресурсов, и время жизни сегментов общей памяти автоматически распределяется между этими процессами. Процессы Python, созданные любым другим способом, получат свой собственный ресурсный трекер при обращении к общей памяти с включенным track. Это приведет к тому, что общая память будет удалена ресурсным трекером первого завершающегося процесса. Чтобы избежать этой проблемы, пользователи subprocess или автономных процессов Python должны установить track на False, когда уже есть другой процесс, который выполняет учет. track игнорируется в Windows, которая имеет свой собственный трекинг и автоматически удаляет разделяемую память, когда все дескрипторы к ней закрыты.

Изменено в версии 3.13: Добавлен параметр track.

close()

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

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

Этот метод не действует в Windows, где единственным способом удаления блока общей памяти является закрытие всех обработчиков.

buf

Просмотр содержимого блока общей памяти.

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])  # Modify multiple at once
>>> buffer[4] = 100                           # Modify single byte at a time
>>> # Attach to an existing shared memory block
>>> shm_b = shared_memory.SharedMemory(shm_a.name)
>>> import array
>>> array.array('b', shm_b.buf[:5])  # Copy the data into a new array.array
array('b', [22, 33, 44, 55, 100])
>>> shm_b.buf[:5] = b'howdy'  # Modify via shm_b using bytes
>>> bytes(shm_a.buf[:5])      # Access via shm_a
b'howdy'
>>> shm_b.close()   # Close each SharedMemory instance
>>> shm_a.close()
>>> shm_a.unlink()  # Call unlink only once to release the shared memory

Следующий пример демонстрирует практическое использование класса SharedMemory с NumPy arrays, доступ к одному и тому же numpy.ndarray из двух разных оболочек Python:

>>> # In the first Python interactive shell
>>> import numpy as np
>>> a = np.array([1, 1, 2, 3, 5, 8])  # Start with an existing NumPy array
>>> from multiprocessing import shared_memory
>>> shm = shared_memory.SharedMemory(create=True, size=a.nbytes)
>>> # Now create a NumPy array backed by shared memory
>>> b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf)
>>> b[:] = a[:]  # Copy the original data into shared memory
>>> b
array([1, 1, 2, 3, 5, 8])
>>> type(b)
<class 'numpy.ndarray'>
>>> type(a)
<class 'numpy.ndarray'>
>>> shm.name  # We did not specify a name so one was chosen for us
'psm_21467_46075'

>>> # In either the same shell or a new Python shell on the same machine
>>> import numpy as np
>>> from multiprocessing import shared_memory
>>> # Attach to the existing shared memory block
>>> existing_shm = shared_memory.SharedMemory(name='psm_21467_46075')
>>> # Note that a.shape is (6,) and a.dtype is np.int64 in this example
>>> 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])

>>> # Back in the first Python interactive shell, b reflects this change
>>> b
array([  1,   1,   2,   3,   5, 888])

>>> # Clean up from within the second Python shell
>>> del c  # Unnecessary; merely emphasizing the array is no longer used
>>> existing_shm.close()

>>> # Clean up from within the first Python shell
>>> del b  # Unnecessary; merely emphasizing the array is no longer used
>>> shm.close()
>>> shm.unlink()  # Free and release the shared memory block at the very end
class multiprocessing.managers.SharedMemoryManager([address[, authkey]])

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

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

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

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

SharedMemory(size)

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

ShareableList(sequence)

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

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

>>> from multiprocessing.managers import SharedMemoryManager
>>> smm = SharedMemoryManager()
>>> smm.start()  # Start the process that manages the shared memory blocks
>>> 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()  # Calls unlink() on sl, raw_shm, and another_sl

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

>>> with SharedMemoryManager() as smm:
...     sl = smm.ShareableList(range(2000))
...     # Divide the work among two processes, storing partial results in sl
...     p1 = Process(target=do_work, args=(sl, 0, 1000))
...     p2 = Process(target=do_work, args=(sl, 1000, 2000))
...     p1.start()
...     p2.start()  # A multiprocessing.Pool might be more efficient
...     p1.join()
...     p2.join()   # Wait for all work to complete in both processes
...     total_result = sum(sl)  # Consolidate the partial results now in sl

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

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

Предоставляет мутабельный спископодобный объект, в котором все значения, хранящиеся в нем, сохраняются в блоке общей памяти. Это ограничивает хранимые значения следующими встроенными типами данных:

  • int (подписанный 64-битный)

  • float

  • bool

  • str (менее 10 Мбайт каждый при кодировании в UTF-8)

  • bytes (менее 10 Мбайт каждый)

  • None

Он также заметно отличается от встроенного типа list тем, что эти списки не могут менять свою общую длину (т. е. нет append(), insert() и т. д.) и не поддерживают динамическое создание новых экземпляров ShareableList с помощью нарезки.

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

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

Примечание

Известная проблема существует для значений bytes и str. Если они заканчиваются \x00 нулевыми байтами или символами, то они могут быть тихо удалены при извлечении их по индексу из ShareableList. Такое поведение .rstrip(b'\x00') считается ошибкой и может быть устранено в будущем. См. gh-106939.

Для приложений, в которых обрезание нулей в конце строки является проблемой, обойдите ее, всегда безусловно добавляя дополнительный байт не 0 в конец таких значений при хранении и безусловно удаляя его при извлечении:

>>> from multiprocessing import shared_memory
>>> nul_bug_demo = shared_memory.ShareableList(['?\x00', b'\x03\x02\x01\x00\x00\x00'])
>>> nul_bug_demo[0]
'?'
>>> nul_bug_demo[1]
b'\x03\x02\x01'
>>> nul_bug_demo.shm.unlink()
>>> padded = shared_memory.ShareableList(['?\x00\x07', b'\x03\x02\x01\x00\x00\x00\x07'])
>>> padded[0][:-1]
'?\x00'
>>> padded[1][:-1]
b'\x03\x02\x01\x00\x00\x00'
>>> padded.shm.unlink()
count(value)

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

index(value)

Возвращает первую индексную позицию значения. Возвращает ValueError, если значение отсутствует.

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'  # Changing data types is supported as well
>>> 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  # Use of a ShareableList after call to unlink() is unsupported

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

>>> b = shared_memory.ShareableList(range(5))         # In a first process
>>> c = shared_memory.ShareableList(name=b.shm.name)  # In a second process
>>> 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()