L’utilisation de mémoire partagée, ou Shared memory, est une solution efficace pour échanger des données entre différents process d’une même application. Nous allons voir dans ce tutoriel l’utilisation de la librairie disponible en Python (qui existe aussi en C)
Principe de fonctionnement
Lorsqu’un système ou application utilise plusieurs process ou tâches, il est parfois nécessaire de partager des données dynamiques entre les différentes tâches pour cela différentes solutions existent pour la communication inter process (IPC). Ces solutions prennent souvent la forme de stockage auquel chacun des process à accès:
- via des fichiers (txt, csv, json, binaire, etc.)
- via des emplacement mémoire physique
- via des flux de données (pipe, FIFO, etc.)
Le principe de SharedMemory ressemble au fonctionnement de mémoire physique d’ordinateur. La fonction va créer un espace mémoire d’une taille définie à une certaine adresse. N’importe quel process connaissant l’adresse pourra y accéder.
Exemple d’utilisation dans un terminal
Dans un premier terminal, lancer les commandes suivantes pour initialiser la shared_memory
>>> import numpy as np >>> from multiprocessing import shared_memory >>> a = np.array([1, 1, 2, 3, 5, 8]) >>> shm = shared_memory.SharedMemory(create=True, size=a.nbytes) >>> b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf) >>> b[:] = a[:] #now b is linked to whatever happen to a in the sharedmemory >>> b array([1, 1, 2, 3, 5, 8]) >>> shm.name 'wnsm_58bac7dd'
Dans un second terminal, utiliser le nom donné par shm.name (wnsm_58bac7dd dasn l’exemple) pour accéder à la mémoire partagée.
>>> import numpy as np >>> from multiprocessing import shared_memory >>> existing_shm = shared_memory.SharedMemory(name='wnsm_58bac7dd') >>> c = np.ndarray((6,), dtype=np.int64, buffer=existing_shm.buf) >>> c # c is a copy of a in shared memory array([1, 1, 2, 3, 5, 8]) >>> c[-1]=1234 >>> c array([ 1, 1, 2, 3, 5, 1234]) >>> c[0]=26 >>> del c >>> existing_shm.close()
Vous pouvez modifier les éléments de c et observer les répercutions sur a dans le premier terminal
>>> b array([ 1, 1, 2, 3, 5, 1234]) >>> b array([ 26, 1, 2, 3, 5, 1234]) >>> del b
Utilisation d’une mémoire partagée entre deux process
Le premier process va créer la mémoire partagée à une adresse connue (« my_unique_memory »)
import sys import numpy as np from multiprocessing import shared_memory # Now create a structure to be exchanged via shared memory a = np.zeros((6,)) a = np.array([1, 1, 2, 3, 5, 8]) try: shm = shared_memory.SharedMemory(name="my_unique_memory",create=True, size=sys.getsizeof(a)) if shm is not None: print(f"Shared memory created {shm.name}") except: print("Shared memory already exists, exiting") exit(1) b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf) b[:] = a[:] # Copy the original data into shared memory try: i=0 while True: #check if data changed if not (b==a).all(): print(b) a[:]=b[:] i+=1 except KeyboardInterrupt: print('interrupted!') del b # Clean up shared memory shm.close() shm.unlink() # Clean up shared memory shm.close() shm.unlink()
Le second process va se connecter à la mémoire partagée et modifier la valeur de l’array enregistrée
import numpy as np from multiprocessing import shared_memory a = np.zeros((6,)) try: existing_shm = shared_memory.SharedMemory(name="my_unique_memory") if existing_shm is not None: print(f"Shared memory connected {existing_shm.name}") except: print("Shared memory does not exist, exiting") exit(1) b = np.ndarray(a.shape, dtype=np.int64, buffer=existing_shm.buf) try: i=0 while True: if(i%100==0): b[-1]=b[-1]+100 if(b[-1]>65535): b[-1]=0 a[:]=b[:] i+=1 except KeyboardInterrupt: print('interrupted!') del b # Clean up shared memory existing_shm.close() existing_shm.unlink() # Clean up shared memory existing_shm.close() existing_shm.unlink()
N.B.: Pour que ce programme fonctionne correctement, le code maître (création de la mémoire) doit être lancé avant le code esclave (lecture et modification de la mémoire)
Bonus: gestion d’erreur de création
Il est possible de créer la même initialisation de mémoire partagée pour le code maître et esclave afin qu’il puisse être exécuté indépendamment de l’ordre d’exécution.
try: shm = shared_memory.SharedMemory(name="my_unique_memory",create=True, size=sys.getsizeof(a)) if shm is not None: print(f"Shared memory created {shm.name}") except FileExistsError: shm = shared_memory.SharedMemory(name="my_unique_memory") if shm is not None: print(f"Shared memory connected {shm.name}") except FileNotFoundError: print("Cannot connect to share memory, exiting") exit(1)
Notion clés
- Gestion de ressources
toujours appeler shm.close et shm.unlink après utilisation pour libérer l’espace mémoire
- Synchronisation des process
Utilisation de verrou lorsque plusieurs process veulent accéder à la même ressource en même temps
- Gestion d’erreur
Les tentatives de lecture d’une mémoire non existante ou déjà libérée entraineront une erreur à gérer.