Site icon AranaCorp

Utilisation de mémoire pour le partage de données en Python

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:

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

toujours appeler shm.close et shm.unlink après utilisation pour libérer l’espace mémoire

Utilisation de verrou lorsque plusieurs process veulent accéder à la même ressource en même temps

Les tentatives de lecture d’une mémoire non existante ou déjà libérée entraineront une erreur à gérer.

Sources

Quitter la version mobile