"multiprocessing.shared_memory" — Mémoire partagée en accès direct depuis plusieurs processus
*********************************************************************************************

**Code source :** Lib/multiprocessing/shared_memory.py

Nouveau dans la version 3.8.

======================================================================

Ce module fournit une classe, "SharedMemory", pour l'allocation et la
gestion de mémoire partagée entre un ou plusieurs processus sur une
machine à plusieurs cœurs ou à multiprocesseurs (architecture
*symmetric multiprocessor* ou SMP). Pour faciliter la gestion du cycle
de vie de la mémoire partagée, tout particulièrement entre plusieurs
processus, le module "multiprocessing.managers" fournit aussi la
classe "SharedMemoryManager", sous-classe de "BaseManager".

Dans ce module, il faut entendre « mémoire partagée » au sens de «
blocs de mémoire partagée à la mode System V » (même si
l'implémentation peut différer), et non au sens de « mémoire
distribuée ». Ce type de mémoire partagée permet à plusieurs processus
d'écrire dans une zone commune (ou « partagée ») de la mémoire vive.
Normalement, les processus n'ont accès qu'à leur propre espace mémoire
; la mémoire partagée permet justement le partage de données entre des
processus, ce qui leur évite d'avoir à s'envoyer ces données par
message. Échanger des données par mémoire partagée peut amener des
gains de performance substantiels par rapport aux échanges via le
disque dur, des connecteurs ou d'autres canaux qui nécessitent de
sérialiser et de désérialiser les données.

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

   Crée un nouveau bloc de mémoire partagée ou enregistre un bloc déjà
   existant. Un nom unique doit être donné à chaque bloc de mémoire
   partagée ; ainsi, un processus peut créer un nouveau bloc de
   mémoire partagée avec un nom fixé et un autre processus peut
   enregistrer le même bloc, à partir de son nom.

   Puisque qu'il permet de partager des données entre processus, un
   bloc de mémoire partagée peut survivre au processus qui l'a créé.
   Lorsqu'un processus n'a plus besoin d'un bloc — qui peut toujours
   être en cours d'utilisation par un autre — il doit appeler la
   méthode "close()". Quand tous les processus ont fini d'utiliser ce
   bloc, il faut appeler la méthode "unlink()" pour le libérer.

   *name* est le nom (une chaîne de caractères) unique de la mémoire
   partagée à allouer. Lors de la création d'un nouveau bloc mémoire,
   si "None" (valeur par défaut) est passé comme nom, un nouveau nom
   est généré.

   *create* indique si un nouveau bloc doit être alloué ("True") ou si
   on enregistre un bloc déjà existant ("False").

   *size* définit le nombre d'octets à allouer. Comme certaines
   plates-formes choisissent d'allouer les blocs mémoire en multiples
   de la taille de la page mémoire de la plate-forme, la taille
   réellement allouée peut être supérieure à la taille demandée. Lors
   de l'enregistrement d'un bloc déjà existant, le paramètre "size"
   est ignoré.

   close()

      Empêche les accès ultérieurs à la mémoire partagée depuis cette
      instance ; toutes les instances doivent appeler "close()" pour
      s'assurer que les ressources sont bien libérées. Notez
      qu'appeler "close()" ne libère pas la mémoire elle-même.

   unlink()

      Initie la libération de la mémoire partagée sous-jacente. Pour
      être sûr que les ressources sont libérées correctement,
      "unlink()" doit être appelée une (et une seule) fois par tous
      les processus qui ont utilisé le bloc partagé. Après avoir
      initié la destruction d'un bloc mémoire, le bloc peut ne pas
      être détruit immédiatement ; ce comportement dépend de la plate-
      forme. Accéder aux données d'un bloc de mémoire partagée après
      l'appel à "unlink()" peut provoquer une erreur mémoire. Notez
      que le dernier processus à libérer le bloc mémoire de mémoire
      partagée peut appeler "unlink()" et "close()" dans n'importe
      quel ordre.

   buf

      Une *memoryview* du contenu du bloc de mémoire partagée.

   name

      Nom unique du bloc de mémoire partagée (lecture seule).

   size

      Taille en octets du bloc de mémoire partagée (lecture seule).

L'exemple qui suit montre un exemple d'utilisation bas niveau
d'instances 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

Le code qui suit est un exemple d'utilisation réel de la classe
"SharedMemory" avec des tableaux NumPy qui accèdent au même
"numpy.ndarray" depuis deux invites Python différentes :

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

   Une sous-classe de "BaseManager" pour gérer des blocs de mémoire
   partagée entre processus.

   Un appel à "start()" depuis une instance "SharedMemoryManager"
   lance un nouveau processus dont le seul but est de gérer le cycle
   de vie des blocs mémoires qu'il a créés. La méthode "shutdown()" de
   l'instance déclenche la libération de tous les blocs mémoires gérés
   par ce processus. Elle appelle "SharedMemory.unlink()" sur tous les
   objets "SharedMemory" gérés par ce processus et l'arrête ensuite.
   Créer des instances de "SharedMemory" par l'intermédiaire d'un
   "SharedMemoryManager" évite d'avoir à gérer et à libérer
   manuellement les ressources mémoire partagées.

   Cette classe fournit des méthodes pour créer et renvoyer des
   instances de "SharedMemory" et pour créer des objets compatibles
   liste ("ShareableList") basés sur la mémoire partagée.

   Référez-vous à "multiprocessing.managers.BaseManager" pour la
   description des arguments optionnels hérités *address* et
   *authkey*, et comment ceux-ci doivent être utilisés pour
   enregistrer un service de "SharedMemoryManager" depuis un autre
   processus.

   SharedMemory(size)

      Crée et renvoie un nouvel objet "SharedMemory" de taille "size"
      octets.

   ShareableList(sequence)

      Crée et renvoie un nouvel objet "ShareableList", initialisé à
      partir des valeurs de la "sequence" en entrée.

L'exemple qui suit illustre les mécanismes de base 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

L'exemple suivant montre comment utiliser un objet
"SharedMemoryManager" avec l'instruction "with" pour être sûr que tous
les blocs mémoire sont libérés quand ils ne sont plus nécessaires.
C'est souvent plus pratique que l'exemple précédent :

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

Lors de l'utilisation d'un "SharedMemoryManager" dans une instruction
"with", les blocs de mémoire partagée créés par ce gestionnaire sont
tous libérés quand les instructions à l'intérieur du bloc "with" ont
été exécutées.

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

   Construit un objet muable compatible avec le type liste dont toutes
   les valeurs sont stockées dans un bloc de mémoire partagée. Ceci
   réduit le type les valeurs pouvant être stockées aux types natifs
   "int", "float", "bool", "str" (de moins de 10 Mo chacune), "bytes"
   (de moins de 10 Mo chacun) et "None". Une autre différence majeure
   avec une "list" native réside dans le fait qu'il est impossible de
   changer la taille (c.-à-d. pas d'ajout en fin de liste, ni
   d'insertion etc.) et qu'il n'est pas possible de créer de nouvelles
   instances de "ShareableList" par découpage.

   *sequence* sert à créer une nouvelle "ShareableList" avec des
   valeurs. Mettez-le à "None" pour enregistrer une "ShareableList"
   déjà existante, en renseignant son nom unique.

   *name* est le nom unique de la mémoire partagée demandée, tel que
   décrit dans la définition de "SharedMemory". Pour enregistrer une
   "ShareableList" déjà existante, renseignez le nom unique du bloc de
   mémoire partagée et laissez "sequence" à "None".

   count(value)

      Renvoie le nombre d’occurrences de "value".

   index(value)

      Renvoie l'indice de la première occurrence de "value". Lève une
      "ValueError" si "value" n'est pas présent.

   format

      Attribut en lecture seule contenant le format d’agrégation
      "struct" utilisé par les valeurs déjà stockées.

   shm

      Instance de "SharedMemory" dans laquelle les valeurs sont
      stockées.

L'exemple qui suit illustre un cas d'usage de base d'une instance de
"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

L'exemple ci-dessous montre comment un, deux ou un grand nombre de
processus peuvent accéder à une "ShareableList" commune à partir du
nom du bloc mémoire partagé sous-jacent :

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