Python : Programmation Asynchrone

De Justine's wiki
Aller à la navigation Aller à la recherche

  Les informations sont issues de http://apprendre-python.com/page-programmation-asynchrone-python-thread-threading et de https://openclassrooms.com/fr/courses/235344-apprenez-a-programmer-en-python/2235545-la-programmation-parallele-avec-threading La programmation asynchrone permet de lancer plusieurs processus en simultané, en lançant plusieurs instructions en même temps. Cela peut permettre de gagner du temps.

Cela passe par le module threading.

Je vais utiliser un script plus simple que celui du cours pour présenter ce concept. On va partir de ce script qui affiche les chiffres de 0 à 9, deux fois.

import threading
import sys

def boucle1():
    for i in range(10):
        sys.stdout.write(str(i) + "\n")

def boucle2():
    for i in range(10):
       sys.stdout.write(str(i) + "\n")

boucle1()     #Les deux fonctions sont lancées l'une après l'autre
boucle2()



Sans surprise, ce script émet les deux séries de nombres sur la sortie standard, dans l'ordre. On aurait pu passer par des prints ici, mais le fait d'utiliser la sortie standard se justifie lorsque l'on passe par du threading : les prints ne marchent plus, [je suppose] parce que les threads sont lancés dans un autre terminal?

La version avec thread donne :

import threading
import sys

def boucle1():
    for i in range(10):
        sys.stdout.write(str(i) + "\n")

def boucle2():
    for i in range(10):
       sys.stdout.write(str(i) + "\n")

threading1 = threading.Thread(target=boucle1, args=()) #Les threads sont des objets 
threading2 = threading.Thread(target=boucle2, args=())

threading1.start()  #...lancés par leur fonction start
threading2.start()


La sortie est intéressante:

justine@Justine-pc:~$ python3 testthread.py
0
1
0
2
1
3
4
2
3
#etc

Les nombres ne sortent plus dans l'ordre ! Les deux threads sont lancés quasiment en même temps, et émettent en même temps.

D'autres essais, fait en modifiant le script pour qu'il affiche la boucle dont est issue le string affiché, montrent que parfois les sorties sont dans l'ordre, et parfois non. Il peut alors être important de tenir compte du fait que les deux threads ne sont pas forcément synchronisés. C'est là l'utilité des RLocks, qui permettent de verrouiller un thread ou une partie d'un thread : la partie verrouillée d'un thread ne pourra s'exécuter qu'un seul thread à la fois.

Par exemple :

import threading
import sys
from threading import RLock

verrou = RLock()

def boucle1():
    with verrou:
        for i in range(10):
            sys.stdout.write("Boucle1" + "\n")

def boucle2():
    with verrou:
        for i in range(10):
            sys.stdout.write("Boucle2" + "\n")

#boucle1()     #Les deux fonctions sont lancées l'une après l'autre
#boucle2()

threading1 = threading.Thread(target=boucle1, args=())
threading2 = threading.Thread(target=boucle2, args=())

threading1.start()
threading2.start()</pre>
 

Ici, la sortie sera toujours boucle1, puis boucle2. Ici, on crée un objet RLock nommé verrou. Ensuite, on passe cet objet à chacune de nos fonction via un context manager (un cours là-dessus devrait suivre). Chaque fonction ne pourra s'exécuter que si elle peut verrouiller le lock, ce qui n'est possible qu'une seule fonction à la fois.

Des sous-routine avec async / await

[[1]]

Deux mots-clef ici :

  • async : sert à définir une fonction comme asynchrone, c'est à dire qu'elle va pouvoir générer des sous-routines et recevoir leur réponse (elle fera autre chose en attendant)
  • await : sert à lancer une sous-routine.

C'est assez utile pour faire des sous-threads sans import le module Threading.

Exemple :


async def truc():
    bidule()
    await machine_asynchrone() #Lancer la sous-routine qui consiste à exécuter la fonction machine_asynchrone()
    chose()