# `multiprocessing.shared_memory` --- Shared memory for direct access across processes

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

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

  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 limite le type des 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()

L'exemple ci-dessous montre comment une `ShareableList` (et le `SharedMemory` sous-jacent) peut être sérialisée et désérialisée. Gardez bien à l'esprit que c'est toujours le même objet, car l'objet désérialisé a le même nom unique et est tout simplement attaché à un objet déjà existant du même nom (si cet objet est toujours en vie).

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