multiprocessing.shared_memory — Shared memory for direct access across processes

Quellcode: Lib/multiprocessing/shared_memory.py

Hinzugefügt in Version 3.8.


Dieses Modul stellt eine Klasse, SharedMemory, für die Zuweisung und Verwaltung von Shared Memory bereit, der von einem oder mehreren Prozessen auf einem Mehrkern- oder symmetrischen Mehrprozessorsystem (SMP) zugegriffen werden kann. Um die Lebenszyklusverwaltung von Shared Memory, insbesondere über verschiedene Prozesse hinweg, zu unterstützen, wird im Modul multiprocessing.managers auch eine Unterklasse von BaseManager, SharedMemoryManager, bereitgestellt.

In diesem Modul bezieht sich Shared Memory auf „POSIX-Style“ Shared Memory-Blöcke (obwohl es nicht unbedingt explizit als solche implementiert ist) und nicht auf „distributed shared memory“. Dieser Stil von Shared Memory erlaubt es verschiedenen Prozessen, potenziell auf eine gemeinsame (oder geteilte) Region von flüchtigem Speicher zu lesen und zu schreiben. Prozesse sind konventionell darauf beschränkt, nur auf ihren eigenen Prozessspeicherbereich zugreifen zu können, aber Shared Memory ermöglicht die gemeinsame Nutzung von Daten zwischen Prozessen und vermeidet so die Notwendigkeit, Nachrichten mit diesen Daten zwischen Prozessen zu senden. Das direkte Teilen von Daten über den Speicher kann erhebliche Leistungssteigerungen im Vergleich zum Teilen von Daten über Festplatte, Socket oder andere Kommunikationsmittel, die Serialisierung/Deserialisierung und Kopieren von Daten erfordern, bieten.

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

Erstellt eine Instanz der Klasse SharedMemory, entweder zum Erstellen eines neuen Shared Memory-Blocks oder zum Anhängen an einen bestehenden. Jeder Shared Memory-Block erhält einen eindeutigen Namen. Auf diese Weise kann ein Prozess einen Shared Memory-Block mit einem bestimmten Namen erstellen und ein anderer Prozess kann sich mit demselben Shared Memory-Block unter demselben Namen verbinden.

Als Ressource zur gemeinsamen Nutzung von Daten über Prozesse hinweg können Shared Memory-Blöcke die ursprünglichen Prozesse, die sie erstellt haben, überleben. Wenn ein Prozess keinen Zugriff mehr auf einen Shared Memory-Block benötigt, der möglicherweise noch von anderen Prozessen benötigt wird, sollte die Methode close() aufgerufen werden. Wenn ein Shared Memory-Block von keinem Prozess mehr benötigt wird, sollte die Methode unlink() aufgerufen werden, um eine ordnungsgemäße Bereinigung zu gewährleisten.

Parameter:
  • name (str | None) – Der eindeutige Name für den angeforderten Shared Memory, angegeben als String. Beim Erstellen eines neuen Shared Memory-Blocks wird, wenn None (Standard) für den Namen übergeben wird, ein neuer Name generiert.

  • create (bool) – Steuert, ob ein neuer Shared Memory-Block erstellt wird (True) oder an einem bestehenden Shared Memory-Block angehängt wird (False).

  • size (int) – Die angeforderte Anzahl von Bytes beim Erstellen eines neuen Shared Memory-Blocks. Da einige Plattformen Speicherblöcke basierend auf der Seitengröße des Speichers zuweisen, kann die exakte Größe des Shared Memory-Blocks größer oder gleich der angeforderten Größe sein. Beim Anhängen an einen bestehenden Shared Memory-Block wird der Parameter size ignoriert.

  • track (bool) – Wenn True, wird der Shared Memory-Block bei Plattformen, auf denen das Betriebssystem dies nicht automatisch tut, bei einem Ressourcentracker-Prozess registriert. Der Ressourcentracker stellt die ordnungsgemäße Bereinigung des Shared Memory sicher, selbst wenn alle anderen Prozesse mit Zugriff auf den Speicher beendet werden, ohne dies zu tun. Python-Prozesse, die von einem gemeinsamen Vorfahren mithilfe von multiprocessing-Einrichtungen erstellt werden, teilen sich einen einzigen Ressourcentracker-Prozess, und die Lebensdauer von Shared Memory-Segmenten wird zwischen diesen Prozessen automatisch verwaltet. Python-Prozesse, die auf andere Weise erstellt werden, erhalten ihren eigenen Ressourcentracker, wenn sie mit aktiviertem track auf Shared Memory zugreifen. Dies führt dazu, dass der Shared Memory vom Ressourcentracker des ersten beendeten Prozesses gelöscht wird. Um dieses Problem zu vermeiden, sollten Benutzer von subprocess oder eigenständigen Python-Prozessen track auf False setzen, wenn bereits ein anderer Prozess die Buchführung übernimmt. track wird unter Windows ignoriert, das über eine eigene Nachverfolgung verfügt und Shared Memory automatisch löscht, wenn alle Handles dazu geschlossen wurden.

Geändert in Version 3.13: Der Parameter track wurde hinzugefügt.

close()

Schließt den Dateideskriptor/Handle des Shared Memory von dieser Instanz. close() sollte aufgerufen werden, sobald der Zugriff auf den Shared Memory-Block aus dieser Instanz nicht mehr benötigt wird. Abhängig vom Betriebssystem kann der zugrunde liegende Speicher freigegeben werden oder auch nicht, selbst wenn alle Handles dazu geschlossen wurden. Um eine ordnungsgemäße Bereinigung zu gewährleisten, verwenden Sie die Methode unlink().

Löscht den zugrunde liegenden Shared Memory-Block. Dies sollte nur einmal pro Shared Memory-Block aufgerufen werden, unabhängig von der Anzahl der Handles dazu, auch in anderen Prozessen. unlink() und close() können in beliebiger Reihenfolge aufgerufen werden, aber der Versuch, auf Daten innerhalb eines Shared Memory-Blocks zuzugreifen, nachdem unlink() aufgerufen wurde, kann je nach Plattform zu Speicherzugriffsfehlern führen.

Diese Methode hat unter Windows keine Auswirkung, wo der einzige Weg, einen Shared Memory-Block zu löschen, darin besteht, alle Handles dazu zu schließen.

buf

Eine Memoryview des Inhalts des Shared Memory-Blocks.

name

Schreibgeschützter Zugriff auf den eindeutigen Namen des Shared Memory-Blocks.

size

Schreibgeschützter Zugriff auf die Größe des Shared Memory-Blocks in Bytes.

Das folgende Beispiel demonstriert die Low-Level-Verwendung von SharedMemory-Instanzen.

>>> 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

Das folgende Beispiel demonstriert eine praktische Anwendung der Klasse SharedMemory mit NumPy-Arrays, wobei auf dasselbe numpy.ndarray von zwei verschiedenen Python-Shells zugegriffen wird.

>>> # 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]])

Eine Unterklasse von multiprocessing.managers.BaseManager, die für die Verwaltung von Shared Memory-Blöcken über Prozesse hinweg verwendet werden kann.

Ein Aufruf von start() auf einer Instanz von SharedMemoryManager startet einen neuen Prozess. Dieser neue Prozess dient ausschließlich der Verwaltung der Lebensdauer aller über ihn erstellten Shared Memory-Blöcke. Um die Freigabe aller von diesem Prozess verwalteten Shared Memory-Blöcke auszulösen, rufen Sie shutdown() auf der Instanz auf. Dies löst einen Aufruf von unlink() für alle von diesem Prozess verwalteten SharedMemory-Objekte aus und stoppt dann den Prozess selbst. Durch die Erstellung von SharedMemory-Instanzen über einen SharedMemoryManager vermeiden wir die Notwendigkeit, die Freigabe von Shared Memory-Ressourcen manuell zu verfolgen und auszulösen.

Diese Klasse bietet Methoden zum Erstellen und Zurückgeben von SharedMemory-Instanzen und zum Erstellen eines Listen-ähnlichen Objekts (ShareableList), das von Shared Memory unterstützt wird.

Siehe BaseManager für eine Beschreibung der geerbten optionalen Eingabeargumente address und authkey und wie sie verwendet werden können, um sich von anderen Prozessen aus mit einem vorhandenen SharedMemoryManager-Dienst zu verbinden.

SharedMemory(size)

Erstellt und gibt ein neues SharedMemory-Objekt mit der angegebenen size in Bytes zurück.

ShareableList(sequence)

Erstellt und gibt ein neues ShareableList-Objekt zurück, das mit den Werten aus der Eingabe-sequence initialisiert wird.

Das folgende Beispiel demonstriert die grundlegenden Mechanismen eines 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

Das folgende Beispiel zeigt ein potenziell bequemeres Muster für die Verwendung von SharedMemoryManager-Objekten über die with-Anweisung, um sicherzustellen, dass alle Shared Memory-Blöcke freigegeben werden, nachdem sie nicht mehr benötigt werden.

>>> 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

Wenn ein SharedMemoryManager in einer with-Anweisung verwendet wird, werden die mit diesem Manager erstellten Shared Memory-Blöcke alle freigegeben, wenn der Codeblock der with-Anweisung ausgeführt wurde.

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

Bietet ein veränderbares Listen-ähnliches Objekt, bei dem alle darin gespeicherten Werte in einem Shared Memory-Block gespeichert sind. Dies schränkt speicherbare Werte auf die folgenden integrierten Datentypen ein.

  • int (vorzeichenbehaftet 64-Bit)

  • float

  • bool

  • str (weniger als 10 MB pro Stück bei Kodierung als UTF-8)

  • bytes (weniger als 10 MB pro Stück)

  • None

Es unterscheidet sich auch merklich vom integrierten list-Typ darin, dass diese Listen ihre Gesamtlänge nicht ändern können (d.h. keine append(), insert(), etc.) und die dynamische Erstellung neuer ShareableList-Instanzen durch Slicing nicht unterstützen.

sequence wird zum Befüllen einer neuen ShareableList mit Werten verwendet. Auf None gesetzt, um stattdessen eine bereits vorhandene ShareableList über ihren eindeutigen Shared Memory-Namen anzuhängen.

name ist der eindeutige Name für den angeforderten Shared Memory, wie in der Definition von SharedMemory beschrieben. Beim Anhängen an eine vorhandene ShareableList geben Sie den eindeutigen Namen ihres Shared Memory-Blocks an, während Sie sequence auf None belassen.

Hinweis

Für bytes- und str-Werte existiert ein bekanntes Problem. Wenn sie mit \x00-Null-Bytes oder Zeichen enden, können diese beim Abrufen per Index aus der ShareableList *stumm gestrippt* werden. Dieses Verhalten (`.rstrip(b'\x00')`) wird als Fehler betrachtet und könnte in Zukunft verschwinden. Siehe gh-106939.

Für Anwendungen, bei denen das Entfernen von nachgestellten Null-Bytes ein Problem darstellt, umgehen Sie dies, indem Sie immer bedingungslos ein zusätzliches Nicht-0-Byte am Ende solcher Werte beim Speichern anhängen und es beim Abrufen bedingungslos entfernen.

>>> 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)

Gibt die Anzahl der Vorkommen von value zurück.

index(value)

Gibt die erste Indexposition von value zurück. Löst ValueError aus, wenn value nicht vorhanden ist.

format

Schreibgeschütztes Attribut, das das struct-Packformat enthält, das von allen aktuell gespeicherten Werten verwendet wird.

shm

Die SharedMemory-Instanz, in der die Werte gespeichert sind.

Das folgende Beispiel demonstriert die grundlegende Verwendung einer ShareableList-Instanz.

>>> 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

Das folgende Beispiel zeigt, wie ein, zwei oder viele Prozesse auf dieselbe ShareableList zugreifen können, indem der Name des Shared Memory-Blocks dahinter angegeben wird.

>>> 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()

Die folgenden Beispiele zeigen, dass ShareableList (und die zugrunde liegenden SharedMemory)-Objekte bei Bedarf gepickelt und entpickelt werden können. Beachten Sie, dass es sich immer noch um dasselbe gemeinsam genutzte Objekt handelt. Dies geschieht, weil das deserialisierte Objekt denselben eindeutigen Namen hat und lediglich an ein vorhandenes Objekt mit demselben Namen angehängt wird (wenn das Objekt noch lebt).

>>> 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()