Favicon
NSI Terminale

Correction : Python et les processus

Création de processus-fils

Infos sur le processus en cours :


import os

def info():
    print('PID = ', os.getpid())
    print('PPID = ', os.getppid())

info()		
			

Création d'un processus-fils :


import os

def nv_fils():
    
    fils = os.fork() # récupération de la valeur renvoyée par fork()
    
    if fils == 0 : # on est dans le fils....
        print('Je suis',  os.getpid(), 'le fils de', os.getppid())
    
    else: # on est dans le père...
        print('Je suis',  os.getpid(), 'le père de', fils)
         
nv_fils()			
			

Création de deux fils du même père :


import os

def deux_fils():
    
    fils1 = os.fork() # premier fils
    
    if fils1 == 0 :
        print('Je suis',  os.getpid(), 'le fils de', os.getppid())
    
    else:
        print('Je suis',  os.getpid(), 'le père de', fils1)
        
        fils2 = os.fork() # deuxième fils, créé à partir du père !
        
        if fils2 == 0 :
            print('Je suis',  os.getpid(), 'le fils de', os.getppid())
        
        else:
            print('Je suis',  os.getpid(), 'le père de', fils2)
            
deux_fils()			
			

Difficultés de l'exécution concurrente

Lancement de processus concurrents

Deux processus :


from multiprocessing import Process
from time import sleep
import os

def alpha():
    a = [chr(i) for i in range(65,91)] # tableau des 26 lettres de l'alphabet à partir de leur code ASCII
    for lettre in a:
       print("Je suis le processus ", os.getpid(), " et j'affiche :", lettre)
       sleep(0.02)


# création des processus
p1 = Process(target = alpha)
p2 = Process(target = alpha)

# démarrage des processus
p1.start()
p2.start()

# attente de leur fin d’exécution
p1.join()
p2.join()
			

A
A
B
B
C
C
....

>>>			
			

→ on observe l'affichage en alternance des lettres de l'alphabet, montrant la commutation à tour de rôle de chacun des deux processus s’exécutant "en parallèle".

Plusieurs processus :


from multiprocessing import Process
from time import sleep
import os

def nombre():
    for i in range(11):
        print("Je suis le processus ", os.getpid(), " et j affiche :", i)
        sleep(0.02)

processus = [Process(target = nombre) for i in range(10)] # liste des processus

# création, démarrage des 10 processus
for p in processus:
    p.start()

# attente de la fin des 10 processus
for p in processus:
    p.join()
			

....
Je suis le processus  5384  et j affiche : 5
Je suis le processus  5385  et j affiche : 4
Je suis le processus  5386  et j affiche : 3
Je suis le processus  5387  et j affiche : 2
Je suis le processus  5388  et j affiche : 1
Je suis le processus  5389  et j affiche : 0
Je suis le processus  5382  et j affiche : 8
Je suis le processus  5383  et j affiche : 7
Je suis le processus  5384  et j affiche : 6
Je suis le processus  5385  et j affiche : 5
....				
			

→ on observe un affichage qui devient vite désordonné, traduisant bien la commutation imprévisible de chaque processus par le système d'exploitation; à chaque exécution, l'ordre d'exécution va de plus changer selon les besoins du système, ce sur quoi le programmeur n'a aucune prise !

Plus les processus sont longs à s'exécuter, plus ce caractère imprévisible apparaît : des processus qui s'exécuteraient très rapidement le feraient peut-être dans l'ordre où ils ont été lancés ( et encore, ce n'est pas sûr ! )

On constate en plus parfois des "bugs" d'affichage lorsque deux processus veulent afficher ( donc utiliser la fonction print()) en même temps !

Conclusion : l'exécution concurrente de plusieurs processus est délicate à gérer ! Il va falloir mettre un peu d'ordre dans tout ça...

Le problème de l'accès à des ressources communes


from multiprocessing import Process, Value, Array
from time import sleep

def incr(compteur):
    for i in range(50000):
        compteur.value += 1


compteur = Value('i', 0)

processus = [Process(target = incr, args = [compteur]) for i in range(10)] # liste des 10 processus

# démarrage des 10 processus
for p in processus:
    p.start()

for p in processus:
    p.join()

print(compteur.value)
			

235678

>>> 				
			

10 processus qui incrémentent jusqu'à 50 000 la même variable : celle-ci devrait donc valoir 50 000 ⨯ 10 = 500 000 à la fin de l'exécution des 10 threads.

Or, on observe systématiquement une valeur différente, changeant à chaque exécution du script...

Interprétation : l'incrémentation de la variable compteur nécessite en réalité plusieurs opérations par le processeur, qui doit aller chercher la valeur dans la mémoire, la ranger dans un registre, l'incrémenter, la ranger à nouveau en mémoire...
Au gré de l'exécution des processus et de leur commutation imprévisible par le SE, il se peut donc très bien qu'un autre processus soit déja en train de modifier la variable alors qu'un premier est en train de consulter sa valeur; l'incrémentation de la variable se fait donc sur une valeur qui n'est pas "la bonne" pour le premier processus.

Certaines ( beaucoup...) d'incrémentations se font donc ainsi "écrasées", ce qui fait que la variable compteur n'est pas correctement modifiée en fin de script.

La solution : les verrous


from multiprocessing import Process, Value, Array, Lock
from time import sleep

def incr(compteur):
    for i in range(50000):
        v.acquire()         # acquisition du verrour par un processus
        compteur.value += 1 # SECTION CRITIQUE !
        v.release()         # libération du verrour


compteur = Value('i', 0)
v = Lock() # création d'un verrou ( déverrouillé à sa création )

processus = [Process(target = incr, args = [compteur]) for i in range(10)] # liste des 10 processus

# démarrage des 10 processus
for p in processus:
    p.start()

for p in processus:
    p.join()

print(compteur.value)
			

500000

>>> 				
			

Le verrou garantit l’accès exclusif d'un seul processus à la variable compteur entre la lecture et l'écriture de cette dernière, sans qu'un autre processus puisse "interférer" entre temps.

Le problème de l'interblocage

L'exécution du script se bloque assez rapidement : le blocage mutuel de chaque processus conduit à une boucle sans fin, de laquelle le script ne peut plus sortir, aucun processus ne libérant la ressource qu'un autre attend...