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

   unlink()

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