multiprocessing.shared_memory — Shared memory for direct access across processes

Código fuente: Lib/multiprocessing/shared_memory.py

Nuevo en la versión 3.8.


Este módulo proporciona una clase, SharedMemory, para la asignación y administración de memoria compartida entre uno o más procesos en una máquina con varios núcleos o varios procesadores simétrico (SMP). Para facilitar la gestión del ciclo de vida de la memoria compartida, especialmente entre múltiples procesos, el módulo multiprocessing.managers también proporciona la clase SharedMemoryManager, una subclase de BaseManager.

En este módulo, la memoria compartida se refiere a bloques de memoria compartida de «Sistema estilo V» (aunque no necesariamente se implementa explícitamente como tal) y no se refiere a «memoria compartida distribuida». Este tipo de memoria compartida permite que múltiples procesos lean y escriban en un área común (o compartida) de memoria volátil. Normalmente, los procesos solo tienen acceso a su propio espacio de memoria; la memoria compartida permite compartir datos entre procesos, lo que evita que tengan que enviar estos datos por mensaje. Compartir datos directamente a través de la memoria puede proporcionar importantes beneficios de rendimiento en comparación con compartir datos a través de un disco o socket u otras comunicaciones que requieren la serialización/deserialización y copia de datos.

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

Crea un nuevo bloque de memoria compartida o guarda un bloque ya existente. Se debe dar un nombre único a cada bloque de memoria compartida; por lo tanto, un proceso puede crear un nuevo bloque de memoria compartida con un nombre particular y un proceso diferente se puede conectar a ese mismo bloque de memoria compartida usando ese mismo nombre.

Como un recurso para compartir datos entre procesos, los bloques de memoria compartida pueden sobrevivir al proceso original que los creó. Cuando un proceso ya no necesita acceso a un bloque de memoria compartida que otros procesos aún podrían necesitar, se debe llamar al método close(). Cuando un proceso ya no necesita un bloque de memoria compartida, se debe llamar al método unlink() para garantizar una limpieza adecuada.

name es el nombre único para la memoria compartida solicitada, especificada como una cadena de caracteres. Al crear un nuevo bloque de memoria compartida, si se proporciona None (valor por defecto) para el nombre, se generará un nombre nuevo.

create controla si se crea un nuevo bloque de memoria compartida (True) o si se adjunta un bloque de memoria compartida existente (False).

size especifica el número solicitado de bytes al crear un nuevo bloque de memoria compartida. Debido a que algunas plataformas eligen asignar fragmentos de memoria en función del tamaño de página de memoria de esa plataforma, el tamaño exacto del bloque de memoria compartida puede ser mayor o igual al tamaño solicitado. Cuando se conecta a un bloque de memoria compartida existente, se ignora el parámetro size.

close()

Cierra el acceso a la memoria compartida desde esta instancia. Para garantizar la limpieza adecuada de los recursos, todas las instancias deben llamar a close() una vez que la instancia ya no sea necesaria. Tenga en cuenta que llamar a close() no causa que el bloque de memoria compartida se destruya.

Solicita que se destruya el bloque de memoria compartida subyacente. Para garantizar la limpieza adecuada de los recursos, se debe llamar a unlink() una vez (y solo una vez) en todos los procesos que necesitan el bloque de memoria compartida. Después de solicitar su destrucción, un bloque de memoria compartida puede o no destruirse de inmediato y este comportamiento puede diferir entre plataformas. Los intentos de acceder a los datos dentro del bloque de memoria compartida después de que se haya llamado a unlink() pueden provocar errores de acceso a la memoria. Nota: el último proceso para liberar el bloque de memoria compartida puede llamar a unlink() y close() en cualquier orden.

buf

Un memoryview del contenido del bloque de memoria compartida.

name

Acceso de solo lectura al nombre único del bloque de memoria compartida.

size

Acceso de solo lectura al tamaño en bytes del bloque de memoria compartida.

El siguiente ejemplo muestra el uso de bajo nivel de instancias de 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

The following example demonstrates a practical use of the SharedMemory class with NumPy arrays, accessing the same numpy.ndarray from two distinct Python shells:

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

Una subclase de BaseManager que se puede utilizar para la gestión de bloques de memoria compartida en todos los procesos.

Una llamada al método start() en una instancia de SharedMemoryManager hace que se inicie un nuevo proceso. El único propósito de este nuevo proceso es administrar el ciclo de vida de todos los bloques de memoria compartida creados a través de él. Para activar la liberación de todos los bloques de memoria compartida administrados por ese proceso, llama al método shutdown() en la instancia. Esto desencadena una llamada al método SharedMemory.unlink() en todos los objetos de la clase SharedMemory administrados por ese proceso y luego detiene el proceso en sí. Al crear instancias de SharedMemory a través de un SharedMemoryManager, evitamos la necesidad de rastrear manualmente y activar la liberación de recursos de memoria compartida.

Esta clase proporciona métodos para crear y retornar instancias SharedMemory y para crear un objeto de tipo lista (ShareableList) basados en memoria compartida.

Consulte multiprocessing.managers.BaseManager para obtener una descripción de los argumentos heredados opcionales address y authkey y cómo se deben usar para registrar un servicio SharedMemoryManager desde otro proceso.

SharedMemory(size)

Crea y retorna un nuevo objeto SharedMemory con el tamaño size especificado en bytes.

ShareableList(sequence)

Crea y retorna un nuevo objeto ShareableList, inicializado por los valores de la entrada sequence.

El siguiente ejemplo muestra los mecanismos básicos de 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

El siguiente ejemplo muestra un patrón más conveniente para usar un objeto SharedMemoryManager con la sentencia with para asegurarse de que todos los bloques de memoria se liberen cuando ya no son necesarios. Esto suele ser más práctico que el ejemplo anterior:

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

Cuando se utiliza un SharedMemoryManager en una sentencia with, los bloques de memoria compartida creados por ese administrador se liberan cuando la sentencias dentro del bloque de código with finaliza la ejecución.

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

Construye un objeto mutable compatible con el tipo de lista cuyos valores se almacenan en un bloque de memoria compartida. Esto reduce los valores de tipo que se pueden almacenar solo a tipos de datos nativos int, float, bool, str (menos de 10 MB cada uno), bytes (menos de 10 MB cada uno) y None. Otra diferencia importante con una lista nativa es que es imposible cambiar el tamaño (es decir, sin adición al final de la lista, sin inserción, etc.) y que no es posible crear nuevas instancias de ShareableList mediante la división.

sequence se utiliza para completar una nueva ShareableList con valores. Establezca en None para registrar en su lugar una ShareableList ya existente por su nombre único de memoria compartida.

name es el nombre único para la memoria compartida solicitada, como se describe en la definición de SharedMemory. Al adjuntar a una ShareableList existente, especifique el nombre único de su bloque de memoria compartida mientras deja sequence establecida en None.

count(value)

Retorna el número de ocurrencias de value.

index(value)

Retorna la primera posición del índice de value. Lanza ValueError si value no está presente.

format

Atributo de solo lectura que contiene el formato de empaquetado struct utilizado por todos los valores almacenados actualmente.

shm

La instancia de SharedMemory donde se almacenan los valores.

El siguiente ejemplo muestra el uso básico de una instancia 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

El siguiente ejemplo muestra cómo uno, dos o muchos procesos pueden acceder al mismo ShareableList al proporcionar el nombre del bloque de memoria compartida detrás de él:

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

El siguiente ejemplo demuestra que los objetos ShareableList (y de forma implícita SharedMemory) pueden ser serializados (pickled) y deserializados (unpickled) si es que se necesitan. Nota, Este va a seguir siendo el mismo objeto compartido. Esto sucede, porque el objeto deserializado tiene el mismo nombre único y simplemente se adjunta a un objeto existente con el mismo nombre (si el objeto todavía sigue vivo):

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