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()
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...
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.
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.
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...