multiprocessing.shared_memory — Delat minne för direktåtkomst mellan processer

Källkod: Lib/multiprocessing/shared_memory.py

Tillagd i version 3.8.


Den här modulen innehåller en klass, SharedMemory, för allokering och hantering av delat minne som kan användas av en eller flera processer på en flerkärnig eller symmetrisk multiprocessormaskin (SMP). För att underlätta livscykelhanteringen av delat minne, särskilt mellan olika processer, finns också en underklass till BaseManager, SharedMemoryManager, i modulen multiprocessing.managers.

I den här modulen avser delat minne delade minnesblock i POSIX-stil (även om de inte nödvändigtvis implementeras explicit som sådana) och inte ”distribuerat delat minne”. Denna typ av delat minne gör det möjligt för olika processer att potentiellt läsa och skriva till en gemensam (eller delad) region i det flyktiga minnet. Processer är normalt begränsade till att bara ha tillgång till sitt eget processminnesutrymme, men med delat minne kan data delas mellan processer, vilket gör att man inte behöver skicka meddelanden mellan processerna som innehåller dessa data. Att dela data direkt via minnet kan ge betydande prestandafördelar jämfört med att dela data via disk eller socket eller annan kommunikation som kräver serialisering/deserialisering och kopiering av data.

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

Skapa en instans av SharedMemory-klassen för att antingen skapa ett nytt delat minnesblock eller koppla till ett befintligt delat minnesblock. Varje delat minnesblock tilldelas ett unikt namn. På så sätt kan en process skapa ett shared memory-block med ett visst namn och en annan process kan koppla till samma shared memory-block med samma namn.

Som en resurs för att dela data mellan processer kan delade minnesblock överleva den ursprungliga processen som skapade dem. När en process inte längre behöver tillgång till ett delat minnesblock som fortfarande kan behövas av andra processer, bör metoden close() anropas. När ett delat minnesblock inte längre behövs av någon process bör metoden unlink() anropas för att säkerställa korrekt upprensning.

Parametrar:
  • name (str | None) – Det unika namnet för det begärda delade minnet, angivet som en sträng. Om None (standard) anges för namnet när ett nytt block med delat minne skapas, kommer ett nytt namn att genereras.

  • create (bool) – Styr om ett nytt delat minnesblock ska skapas (True) eller om ett befintligt delat minnesblock ska kopplas till (False).

  • size (int) – Det begärda antalet byte när ett nytt delat minnesblock skapas. Eftersom vissa plattformar väljer att allokera minnesbitar baserat på plattformens minnessidstorlek, kan den exakta storleken på det delade minnesblocket vara större eller lika med den begärda storleken. När du ansluter till ett befintligt delat minnesblock ignoreras parametern size.

  • track (bool) – När True, registrera det delade minnesblocket med en resursspårningsprocess på plattformar där operativsystemet inte gör detta automatiskt. Resursspåraren säkerställer korrekt rensning av det delade minnet även om alla andra processer med tillgång till minnet avslutas utan att göra det. Python-processer som skapats från en gemensam förfader med hjälp av multiprocessing delar en enda resursspårningsprocess, och livstiden för delade minnessegment hanteras automatiskt mellan dessa processer. Python-processer som skapas på något annat sätt kommer att få sin egen resursspårare när de använder delat minne med track aktiverat. Detta kommer att leda till att det delade minnet raderas av resursspåraren för den första process som avslutas. För att undvika detta problem bör användare av subprocess eller fristående Python-processer sätta track till False när det redan finns en annan process på plats som sköter bokföringen. track ignoreras i Windows, som har sin egen spårning och automatiskt raderar delat minne när alla handtag till det har stängts.

Ändrad i version 3.13: Parametern track har lagts till.

close()

Stäng filbeskrivaren/handtaget till det delade minnet från den här instansen. close() bör anropas när åtkomst till det delade minnesblocket från den här instansen inte längre behövs. Beroende på operativsystem kan det hända att det underliggande minnet inte frigörs även om alla handtag till det har stängts. För att säkerställa korrekt upprensning, använd metoden unlink().

Ta bort det underliggande delade minnesblocket. Detta bör bara anropas en gång per delat minnesblock oavsett antalet handtag till det, även i andra processer. unlink() och close() kan anropas i valfri ordning, men om du försöker komma åt data i ett delat minnesblock efter unlink() kan det leda till minnesåtkomstfel, beroende på plattform.

Den här metoden har ingen effekt i Windows, där det enda sättet att ta bort ett delat minnesblock är att stänga alla handtag.

buf

En minnesvy av innehållet i det delade minnesblocket.

name

Skrivskyddad åtkomst till det unika namnet på det delade minnesblocket.

size

Skrivskyddad åtkomst till storleken i bytes på det delade minnesblocket.

Följande exempel demonstrerar lågnivåanvändning av SharedMemory-instanser:

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

Följande exempel visar en praktisk användning av klassen SharedMemory med NumPy matriser, med åtkomst till samma numpy.ndarray från två olika Python-skal:

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

En underklass till multiprocessing.managers.BaseManager som kan användas för hantering av delade minnesblock mellan processer.

Ett anrop till start() på en instans av SharedMemoryManager gör att en ny process startas. Den nya processens enda syfte är att hantera livscykeln för alla delade minnesblock som skapas genom den. För att utlösa frisläppandet av alla delade minnesblock som hanteras av den processen, anropa shutdown() på instansen. Detta utlöser ett unlink()-anrop på alla SharedMemory-objekt som hanteras av den processen och stoppar sedan själva processen. Genom att skapa SharedMemory-instanser genom en SharedMemoryManager slipper vi manuellt spåra och utlösa frigörandet av delade minnesresurser.

Den här klassen innehåller metoder för att skapa och returnera SharedMemory-instanser och för att skapa ett listliknande objekt (ShareableList) som stöds av delat minne.

Se BaseManager för en beskrivning av de ärvda valfria inmatningsargumenten address och authkey och hur de kan användas för att ansluta till en befintlig tjänst SharedMemoryManager från andra processer.

SharedMemory(size)

Skapar och returnerar ett nytt SharedMemory-objekt med den angivna storleken i byte.

ShareableList(sequence)

Skapar och returnerar ett nytt ShareableList-objekt, initierat med värdena från indatans sekvens.

Följande exempel visar de grundläggande mekanismerna i en 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

I följande exempel visas ett potentiellt mer praktiskt mönster för användning av SharedMemoryManager-objekt via with-satsen för att säkerställa att alla delade minnesblock frigörs när de inte längre behövs:

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

När du använder en SharedMemoryManager i en with-sats, frigörs alla delade minnesblock som skapats med den hanteraren när with-satsens kodblock är färdigt exekverat.

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

Tillhandahåller ett föränderligt listliknande objekt där alla värden som lagras i objektet lagras i ett delat minnesblock. Detta begränsar lagringsbara värden till följande inbyggda datatyper:

  • int (signerad 64-bitars)

  • float

  • bool

  • str (mindre än 10M byte vardera när de kodas som UTF-8)

  • bytes (mindre än 10M bytes vardera)

  • Ingen

Den skiljer sig också markant från den inbyggda list-typen genom att dessa listor inte kan ändra sin totala längd (dvs. ingen append(), insert(), etc.) och inte stöder det dynamiska skapandet av nya ShareableList-instanser via slicing.

sequence används för att fylla en ny ShareableList full med värden. Sätt till None för att istället koppla till en redan existerande ShareableList med dess unika namn i det delade minnet.

name är det unika namnet för det begärda delade minnet, enligt beskrivningen i definitionen för SharedMemory. När du ansluter till en befintlig ShareableList, ange det unika namnet på det delade minnesblocket medan sequence är satt till None.

Anteckning

Ett känt problem finns för bytes och str-värden. Om de slutar med \x00 nollbytes eller tecken, kan dessa tyst avlägsnas när de hämtas med index från ShareableList. Detta .rstrip(b'\x00')-beteende anses vara en bugg och kan försvinna i framtiden. Se gh-106939.

För applikationer där rstripping av efterföljande nollor är ett problem, kan du kringgå det genom att alltid ovillkorligen lägga till en extra icke-0-byte i slutet av sådana värden vid lagring och ovillkorligen ta bort den vid hämtning:

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

Returnera antalet förekomster av value.

index(value)

Returnerar första indexpositionen för value. Utlöser ValueError om value inte finns.

format

Skrivskyddat attribut som innehåller förpackningsformatet struct som används av alla aktuella lagrade värden.

shm

Den SharedMemory-instans där värdena lagras.

Följande exempel visar grundläggande användning av en ShareableList-instans:

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

Följande exempel visar hur en, två eller många processer kan komma åt samma ShareableList genom att ange namnet på det bakomliggande delade minnesblocket:

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

Följande exempel visar att ShareableList (och underliggande SharedMemory) objekt kan picklas och unpicklas vid behov. Notera, att det fortfarande kommer att vara samma delade objekt. Detta händer eftersom det deserialiserade objektet har samma unika namn och bara är kopplat till ett befintligt objekt med samma namn (om objektet fortfarande är vid liv):

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