FORMATION PYTHON

Formation Python

Année 2024

_

FORMATION PYTHON

En python, le terme multiprogramming, couvrent 3 façons de programmer bien distinctes:

  • le multithreading: gestion de fil d'exécution
  • le multiprocessing: gestion des processus
  • la programmation asynchrone: les taches, coroutines et les futures

Même si les principes de mise en oeuvre diffèrent, l'ensemble des outils (classes)
présentées ici est commun à tous ces modes de multi-programmation.

Dans la multi-programmation (plus avec des processus ou des fils d'exécution), le terme de synchronisation se réfère à deux concepts distincts (mais liés) :

  • la synchronisation pour l'accès aux données
  • la synchronisation des unités de traitements (Threads, processus, tàches).
FORMATION PYTHON

Fils d'exécution (Thread).

Tout processus possède un thread principal qui lui-même, peut lancer d'autres threads.

  • Chaque thread à sa propre pile pour ses variables locales et les passages de paramètres
  • Les variables globales sont partagées par tous les threads du processus.

Le temps du processus est partagée par tous les threads. Chacun aura un peu de temps mais on ne sait pas comment le temps est alloué à chaque thread. En général, dès qu'il y a une I/O, le thread est mis en attente, et du temps est donné à un autre. Ce changement de thread est connu sous le nom de changement de contexte. Ce temps de traitement n'est pas négligeable.

FORMATION PYTHON

Synchronisation de données:

Ces éléments permettent de garantir:

  • des accès en consultation et mises à jour de données sécuriséss avec:
    • Des verrous: Lock, RLock,
    • Des sémaphores: Semaphore, BoundSemaphore,
    • Des files d'attente: (Fifo)Queue, des piles: LifoQueue
FORMATION PYTHON

Les Lock, RLock

2 méthodes acquire et release sont disponibles. Les Lock sont utilsés comme des Mutex et permettent un accès protégé (Un seul thread ou fil à la fois) à une ressource.

l = Lock()
....
l.acquire()
# l.acquire() ici ce 2° appel bloquerait.
...
l.release()

Le RLock est un lock mais permet des appels multiples à acquire dans un même fil. Il faudra alors autant d'appels à release que à acquire.

l = Lock()
....
l.acquire()
l.acquire() # ici ce 2° appel ne bloque pas.

...

l.release()
l.release()
FORMATION PYTHON

Les Semaphore

Un sémaphore est comme un Lock mais avec une valeur initiale qui peut être supérieure à 1.

2 méthodes acquire et release sont disponibles. La première décrémente la valeur courante, la deuxième méthode l'incrémente.

Quand la valeur courante est à 0, le sémaphore bloque le fil d'exécution appelant, attendant qu'un autre fil ait appelé la mèthode release.

Les objets de cette classe peuvent s'utiliser comme compteur partagé

Les BoudedSemaphore

Ce sémaphore d'utilise comme le sémaphore mais avec une borne supérieure qui ne doit jamais être dépassée. Cette valeur est fournie à la création de l'objet.

FORMATION PYTHON

Les risques liés à une mise en oeuvre défectueuse de ces éléments de synchronisation sont:

  • Interblocage (deadlocks: Ce phénomène d’interblocage est le problème le plus courant. L’inter-blocage se produit lorsque deux threads concurrents s’attendent mutuellement. Les threads bloqués dans cet état le sont définitivement.
  • Famine (starvation): Un processus léger ne pouvant jamais accéder à un verrou se trouve dans une situation de famine. Cette situation se produit, lorsqu’un processus léger, prêt à être exécuté, est toujours devancé par un autre processus léger plus prioritaire.
  • Endormissement (dormancy): cas d’un processus léger suspendu qui n’est jamais réveillé (Peu probable en python).

Notion de chien de garde (watchdog)

Un chien de garde est une technique logicielle utilisée pour s’assurer qu’un programme ne reste pas bloqué à une étape particulière du traitement qu’il effectue. C’est une protection destinée généralement à redémarrer le système (relancer un thread ou un processus).

FORMATION PYTHON

Synchronisation de threads

Les outils disponibles en python sont:

  • Des conditions: classe Condition,
  • Des évènements: classe Event,
  • Des barrières: classe Barrier.

Tous ces classes possèdent une méthode wait qui permet d'attendre qu'un évènement débloque les fils d'exécutions en attente.

FORMATION PYTHON
Les Condition.

2 primitives (méthode) d'attente: waitet wait_for. 2 primitives de déblocage que sont notify et notify_all.
Attention les Conditions fonctionnent avec un verrou (Lock) fourni ou créé par défaut. Le verrou doit être acquis avant l'appel à toutes les méthodes.

Les Event.

1 primitive d'attente wait et des méthodes qui permenttent de manipuler un booléen partagé.
set, is_set, clear. la méthode set libére tous les threads en attente.

Les Barrier.

Une seule primitive wait, tous les threads qui appelent cette méthode sont en attente et sont libérés quand un seuil de threads bloqués est atteint. Cette valeur est fixée au moment de la création de la barrière.

FORMATION PYTHON

Processus.

3 modes de démarrage qui sont liés à OS dans lequel est lancé l'interpréteur.

  • spawn
  • fork
  • forkserver

Attention la gestion des processus est piloté par l'OS. A ce titre il faut se référencer à votre OS pour ce qui concerne les propriétés et règles d' 'échange entre les processus "père" et les processus "fils".

Toutes les primitives vues sur les fils d'exécution/thread sont dispobibles en Multiprocessing et s'utlisent de la même façon.

Voir la doc

FORMATION PYTHON

Coroutines et Taches asynchrones.

Une coroutine est une fonction qui contient une série d'instructions définies dans une fonction. Elle est préfixée au niveau de sa définition par le mot-clé async.

Si on lance test(q), cette coroutine ne s'exécute pas, cela correspond juste à la création d'une coroutine.

async def test(q):
      item = await q.get()
      if item is not None:
        return item

      raise asyncio.Empty

async def run_it():
  q = asyncio.Queue()
  await q.put(10)
  print(await test(q))
  await q.put(20)
  t = asyncio.create_task(test(q)) # ici je crée une tâche
  print(await t)

asyncio.run(run_it())

Une coroutine doit avoir au moins une mise en attente via le mot-clé await.

FORMATION PYTHON

La règle:

Entre 2 mises en attente, le systême asynchrone garantit que la coroutine/tâche ne sera jamais interrompue.

Cette garantie vient du fait les tâches/coroutine sont stockées dans une file d'attente qui est gérée par une boucle dédiée . La file d'attente est unique pour un et un seul thread. Même si le thread est interrompu, la tache reprendra son exécution.

Une tâche est toujours créée à partir d'une coroutine. Elle propose une interface beaucoup plus complète.

La Task, la coroutine sont des objets awaitables à l'instar des Futures qui sont des objets bas niveau utilisés dans les classes de synchronisation comme le Semaphore.

Une tâche peut être dans l'un de ces 4 états:

  • En attente: PENDING,
  • Terminée: FINISHED, évaluable via la mèthode done():
    • avec un résultat, qui est récupérable via la méthode result().
    • avec une erreur (exception levée), qui est récupérable via la mèthode exception().
  • Annulée:CANCELLED, évaluable via la méthode cancelled() et qui lève une exception CncelledError.
FORMATION PYTHON

Lanceurs/pools de threads, de processus.

Ces lanceurs (Executor) permettent de lancer plusieursinstances d'un thread ou d'un processus et de récupérer les résultats pour taitements ultérieurs.

Les lanceurs de threads.

Lance un nombre fixe de threads.
Voir la doc

Les pools de processus.

Voir les pools de processus

Les lanceurs de processus.

Les ProcessPoolExecutor sont mises en oeuvre en s'appuyant des pools de processus. Voir cet Executor

Il existe aussi en multiprocessing des managers qui permettent de répartir des processus sur plusieurs machines. Voir les Managers

FORMATION PYTHON

Multiprogramming en Python

Le multiprogramming permet de découper les traitements en unité logique, voire plus élémentaires, et de les exécuter de manière parallèle, de répartir les traitements.

Les cas d'usage sont:

  • CPU-bound -> multiprocessing
  • I/O-bound, fast I/O, Limited number of Connections -> multithreading
  • I/O-bound, slow I/O, many connections -> asyncio