Concurrence en Python - Multitraitement

Dans ce chapitre, nous nous concentrerons davantage sur la comparaison entre le multitraitement et le multithreading.

Multitraitement

Il s'agit de l'utilisation de deux unités de processeur ou plus dans un même système informatique. C'est la meilleure approche pour tirer le plein potentiel de notre matériel en utilisant le nombre total de cœurs de processeur disponibles dans notre système informatique.

Multithreading

C'est la capacité d'un processeur à gérer l'utilisation du système d'exploitation en exécutant plusieurs threads simultanément. L'idée principale du multithreading est de réaliser le parallélisme en divisant un processus en plusieurs threads.

Le tableau suivant montre certaines des différences importantes entre eux -

Multitraitement Multiprogrammation
Le multitraitement fait référence au traitement de plusieurs processus en même temps par plusieurs processeurs. La multiprogrammation conserve plusieurs programmes dans la mémoire principale en même temps et les exécute simultanément en utilisant un seul processeur.
Il utilise plusieurs processeurs. Il utilise un seul processeur.
Il permet un traitement parallèle. Le changement de contexte a lieu.
Moins de temps pour traiter les travaux. Plus de temps pour traiter les travaux.
Il facilite une utilisation beaucoup plus efficace des appareils du système informatique. Moins efficace que le multitraitement.
Habituellement plus cher. De tels systèmes sont moins chers.

Élimination de l'impact du verrouillage global des interprètes (GIL)

Lorsque vous travaillez avec des applications simultanées, il existe une limitation présente dans Python appelée GIL (Global Interpreter Lock). GIL ne nous permet jamais d'utiliser plusieurs cœurs de CPU et nous pouvons donc dire qu'il n'y a pas de vrais threads en Python. GIL est le mutex - verrou d'exclusion mutuelle, qui sécurise les threads. En d'autres termes, nous pouvons dire que GIL empêche plusieurs threads d'exécuter du code Python en parallèle. Le verrou ne peut être détenu que par un seul thread à la fois et si nous voulons exécuter un thread, il doit d'abord acquérir le verrou.

Avec l'utilisation du multitraitement, nous pouvons efficacement contourner la limitation causée par GIL -

  • En utilisant le multitraitement, nous utilisons la capacité de plusieurs processus et, par conséquent, nous utilisons plusieurs instances du GIL.

  • Pour cette raison, il n'y a aucune restriction d'exécution du bytecode d'un thread dans nos programmes à tout moment.

Démarrage des processus en Python

Les trois méthodes suivantes peuvent être utilisées pour démarrer un processus en Python dans le module multitraitement -

  • Fork
  • Spawn
  • Forkserver

Créer un processus avec Fork

La commande Fork est une commande standard trouvée sous UNIX. Il est utilisé pour créer de nouveaux processus appelés processus enfants. Ce processus enfant s'exécute en même temps que le processus appelé processus parent. Ces processus enfants sont également identiques à leurs processus parents et héritent de toutes les ressources disponibles pour le parent. Les appels système suivants sont utilisés lors de la création d'un processus avec Fork -

  • fork()- C'est un appel système généralement implémenté dans le noyau. Il est utilisé pour créer une copie du processus.p>

  • getpid() - Cet appel système renvoie l'ID de processus (PID) du processus appelant.

Exemple

L'exemple de script Python suivant vous aidera à comprendre comment créer un nouveau processus enfant et obtenir les PID des processus enfants et parents -

import os

def child():
   n = os.fork()
   
   if n > 0:
      print("PID of Parent process is : ", os.getpid())

   else:
      print("PID of Child process is : ", os.getpid())
child()

Production

PID of Parent process is : 25989
PID of Child process is : 25990

Créer un processus avec Spawn

Spawn signifie commencer quelque chose de nouveau. Par conséquent, engendrer un processus signifie la création d'un nouveau processus par un processus parent. Le processus parent continue son exécution de manière asynchrone ou attend que le processus enfant termine son exécution. Suivez ces étapes pour générer un processus -

  • Importation du module multitraitement.

  • Création du processus d'objet.

  • Démarrage de l'activité de processus en appelant start() méthode.

  • Attendre que le processus ait terminé son travail et quitter en appelant join() méthode.

Exemple

L'exemple suivant de script Python aide à générer trois processus

import multiprocessing

def spawn_process(i):
   print ('This is process: %s' %i)
   return

if __name__ == '__main__':
   Process_jobs = []
   for i in range(3):
   p = multiprocessing.Process(target = spawn_process, args = (i,))
      Process_jobs.append(p)
   p.start()
   p.join()

Production

This is process: 0
This is process: 1
This is process: 2

Créer un processus avec Forkserver

Le mécanisme de Forkserver n'est disponible que sur les plates-formes UNIX sélectionnées qui prennent en charge le passage des descripteurs de fichiers sur les tuyaux Unix. Considérez les points suivants pour comprendre le fonctionnement du mécanisme Forkserver -

  • Un serveur est instancié en utilisant le mécanisme Forkserver pour démarrer un nouveau processus.

  • Le serveur reçoit alors la commande et gère toutes les demandes de création de nouveaux processus.

  • Pour créer un nouveau processus, notre programme python enverra une requête à Forkserver et il créera un processus pour nous.

  • Enfin, nous pouvons utiliser ce nouveau processus créé dans nos programmes.

Processus démon en Python

Python multiprocessingmodule nous permet d'avoir des processus démons via son option démoniaque. Les processus démons ou les processus qui s'exécutent en arrière-plan suivent un concept similaire à celui des threads démons. Pour exécuter le processus en arrière-plan, nous devons définir l'indicateur démoniaque sur true. Le processus démon continuera à s'exécuter tant que le processus principal est en cours d'exécution et il se terminera après avoir terminé son exécution ou lorsque le programme principal serait tué.

Exemple

Ici, nous utilisons le même exemple que celui utilisé dans les threads du démon. La seule différence est le changement de module demultithreading à multiprocessinget définissant l'indicateur démoniaque sur true. Cependant, il y aurait un changement de sortie comme indiqué ci-dessous -

import multiprocessing
import time

def nondaemonProcess():
   print("starting my Process")
   time.sleep(8)
   print("ending my Process")
def daemonProcess():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonProcess = multiprocessing.Process(target = nondaemonProcess)
   daemonProcess = multiprocessing.Process(target = daemonProcess)
   daemonProcess.daemon = True
   nondaemonProcess.daemon = False
   daemonProcess.start()
   nondaemonProcess.start()

Production

starting my Process
ending my Process

La sortie est différente par rapport à celle générée par les threads de démon, car le processus en mode sans démon a une sortie. Par conséquent, le processus démoniaque se termine automatiquement après la fin des programmes principaux pour éviter la persistance des processus en cours d'exécution.

Terminer les processus en Python

Nous pouvons tuer ou terminer un processus immédiatement en utilisant le terminate()méthode. Nous utiliserons cette méthode pour terminer le processus fils, qui a été créé à l'aide de la fonction, immédiatement avant de terminer son exécution.

Exemple

import multiprocessing
import time
def Child_process():
   print ('Starting function')
   time.sleep(5)
   print ('Finished function')
P = multiprocessing.Process(target = Child_process)
P.start()
print("My Process has terminated, terminating main thread")
print("Terminating Child Process")
P.terminate()
print("Child Process successfully terminated")

Production

My Process has terminated, terminating main thread
Terminating Child Process
Child Process successfully terminated

La sortie montre que le programme se termine avant l'exécution du processus enfant qui a été créé à l'aide de la fonction Child_process (). Cela implique que le processus enfant s'est terminé avec succès.

Identifier le processus actuel en Python

Chaque processus du système d'exploitation possède une identité de processus appelée PID. En Python, nous pouvons connaître le PID du processus actuel à l'aide de la commande suivante -

import multiprocessing
print(multiprocessing.current_process().pid)

Exemple

L'exemple suivant de script Python aide à trouver le PID du processus principal ainsi que le PID du processus enfant -

import multiprocessing
import time
def Child_process():
   print("PID of Child Process is: {}".format(multiprocessing.current_process().pid))
print("PID of Main process is: {}".format(multiprocessing.current_process().pid))
P = multiprocessing.Process(target=Child_process)
P.start()
P.join()

Production

PID of Main process is: 9401
PID of Child Process is: 9402

Utilisation d'un processus dans une sous-classe

Nous pouvons créer des threads en sous-classant le threading.Threadclasse. De plus, nous pouvons également créer des processus en sous-classant lesmultiprocessing.Processclasse. Pour utiliser un processus dans une sous-classe, nous devons considérer les points suivants -

  • Nous devons définir une nouvelle sous-classe du Process classe.

  • Nous devons remplacer le _init_(self [,args] ) classe.

  • Nous devons remplacer le de la run(self [,args] ) méthode pour mettre en œuvre quoi Process

  • Nous devons démarrer le processus en invoquant lestart() méthode.

Exemple

import multiprocessing
class MyProcess(multiprocessing.Process):
   def run(self):
   print ('called run method in process: %s' %self.name)
   return
if __name__ == '__main__':
   jobs = []
   for i in range(5):
   P = MyProcess()
   jobs.append(P)
   P.start()
   P.join()

Production

called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5

Module multiprocesseur Python - Classe de pool

Si nous parlons de parallèle simple processingtâches dans nos applications Python, puis le module multiprocesseur nous fournit la classe Pool. Les méthodes suivantes dePool la classe peut être utilisée pour lancer un certain nombre de processus enfants dans notre programme principal

apply () méthode

Cette méthode est similaire à la.submit()méthode de .ThreadPoolExecutor.Il bloque jusqu'à ce que le résultat soit prêt.

méthode apply_async ()

Lorsque nous avons besoin d'une exécution parallèle de nos tâches, nous devons utiliser leapply_async()méthode pour soumettre des tâches au pool. Il s'agit d'une opération asynchrone qui ne verrouille pas le thread principal tant que tous les processus enfants ne sont pas exécutés.

map () méthode

Tout comme le apply()méthode, il bloque également jusqu'à ce que le résultat soit prêt. C'est l'équivalent du intégrémap() fonction qui divise les données itérables en un certain nombre de blocs et les soumet au pool de processus en tant que tâches distinctes.

map_async (), méthode

C'est une variante du map() méthode comme apply_async() est à la apply()méthode. Il renvoie un objet résultat. Lorsque le résultat est prêt, un appelable lui est appliqué. L'appelable doit être terminé immédiatement; sinon, le thread qui gère les résultats sera bloqué.

Exemple

L'exemple suivant vous aidera à implémenter un pool de processus pour effectuer une exécution parallèle. Un simple calcul du carré du nombre a été effectué en appliquant lasquare() fonction à travers le multiprocessing.Poolméthode. ensuitepool.map() a été utilisé pour soumettre le 5, car l'entrée est une liste d'entiers de 0 à 4. Le résultat serait stocké dans p_outputs et il est imprimé.

def square(n):
   result = n*n
   return result
if __name__ == '__main__':
   inputs = list(range(5))
   p = multiprocessing.Pool(processes = 4)
   p_outputs = pool.map(function_square, inputs)
   p.close()
   p.join()
   print ('Pool :', p_outputs)

Production

Pool : [0, 1, 4, 9, 16]