Threading Python : une introduction – toptips.fr

Dans ce didacticiel, vous apprendrez à utiliser le module de threading intégré de Python pour explorer les fonctionnalités de multithreading de Python.

En commençant par les bases des processus et des threads, vous apprendrez comment fonctionne le multithreading en Python, tout en comprenant les concepts de concurrence et de parallélisme. Vous apprendrez ensuite à démarrer et à exécuter un ou plusieurs threads en Python à l’aide du module de threading intégré.

Commençons.

Processus vs threads : quelles sont les différences ?

Qu’est-ce qu’un processus ?

Un processus est une instance d’un programme qui doit s’exécuter.

Cela peut être n’importe quoi – un script Python ou un navigateur Web tel que Chrome à une application de vidéoconférence. Si vous lancez le Gestionnaire des tâches sur votre machine et accédez à Performances -> CPU, vous pourrez voir les processus et les threads en cours d’exécution sur les cœurs de votre CPU.

Comprendre les processus et les threads

En interne, un processus possède une mémoire dédiée qui stocke le code et les données correspondant au processus.

Un processus consiste en un ou plusieurs threads. Un thread est la plus petite séquence d’instructions que le système d’exploitation peut exécuter, et il représente le flux d’exécution.

Chaque thread a sa propre pile et ses propres registres mais pas de mémoire dédiée. Tous les threads associés à un processus peuvent accéder aux données. Par conséquent, les données et la mémoire sont partagées par tous les threads d’un processus.

Dans un processeur à N cœurs, N processus peuvent s’exécuter en parallèle au même moment. Cependant, deux threads du même processus ne peuvent jamais s’exécuter en parallèle, mais peuvent s’exécuter simultanément. Nous aborderons le concept de concurrence par rapport au parallélisme dans la section suivante.

Sur la base de ce que nous avons appris jusqu’à présent, résumons les différences entre un processus et un thread.

FonctionnalitéProcessusThreadMémoireMémoire dédiéeMémoire partagéeMode d’exécutionParallel, concurrentConcurrent ; mais pas parallèleExécution gérée par le système d’exploitationCPython Interpreter

Multithreading en Python

En Python, le Global Interpreter Lock (GIL) garantit qu’un seul thread peut acquérir le verrou et s’exécuter à tout moment. Tous les threads doivent acquérir ce verrou pour s’exécuter. Cela garantit qu’un seul thread peut être en cours d’exécution – à un moment donné – et évite le multithreading simultané.

Par exemple, considérons deux threads, t1 et t2, du même processus. Étant donné que les threads partagent les mêmes données lorsque t1 lit une valeur particulière k, t2 peut modifier la même valeur k. Cela peut conduire à des blocages et à des résultats indésirables. Mais un seul des threads peut acquérir le verrou et s’exécuter à n’importe quelle instance. Par conséquent, GIL garantit également la sécurité des fils.

Alors, comment obtenons-nous des capacités de multithreading en Python ? Pour comprendre cela, discutons des concepts de concurrence et de parallélisme.

Concurrence contre parallélisme : un aperçu

Considérez un processeur avec plus d’un cœur. Dans l’illustration ci-dessous, le CPU a quatre cœurs. Cela signifie que nous pouvons avoir quatre opérations différentes exécutées en parallèle à un instant donné.

S’il y a quatre processus, chacun des processus peut s’exécuter indépendamment et simultanément sur chacun des quatre cœurs. Supposons que chaque processus a deux threads.

Pour comprendre comment fonctionne le threading, passons d’une architecture de processeur multicœur à une architecture monocœur. Comme mentionné, un seul thread peut être actif sur une instance d’exécution particulière ; mais le cœur du processeur peut basculer entre les threads.

Par exemple, les threads liés aux E/S attendent souvent les opérations d’E/S : lecture dans l’entrée utilisateur, lectures de base de données et opérations sur les fichiers. Pendant ce temps d’attente, il peut libérer le verrou afin que l’autre thread puisse s’exécuter. Le temps d’attente peut aussi être une opération simple comme dormir pendant n secondes.

En résumé : lors des opérations d’attente, le thread libère le verrou, permettant au cœur du processeur de basculer vers un autre thread. Le thread précédent reprend son exécution une fois la période d’attente terminée. Ce processus, où le cœur du processeur bascule simultanément entre les threads, facilite le multithreading. ✅

Si vous souhaitez implémenter le parallélisme au niveau du processus dans votre application, envisagez plutôt d’utiliser le multitraitement.

Module de threading Python : premiers pas

Python est livré avec un module de threading que vous pouvez importer dans le script Python.

import threading

Pour créer un objet thread en Python, vous pouvez utiliser le constructeur Thread : threading.Thread(…). C’est la syntaxe générique qui suffit pour la plupart des implémentations de thread :

threading.Thread(target=...,args=...)

Ici,

  • target est l’argument mot-clé indiquant un appelable Python
  • args est le tuple d’arguments que la cible prend.

Vous aurez besoin de Python 3.x pour exécuter les exemples de code de ce didacticiel. Téléchargez le code et suivez-le.

Comment définir et exécuter des threads en Python

Définissons un thread qui exécute une fonction cible.

La fonction cible est some_func.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

Analysons ce que fait l’extrait de code ci-dessus :

  • Il importe le threading et les modules de temps.
  • La fonction some_func a des instructions descriptives print() et inclut une opération de mise en veille pendant deux secondes : time.sleep(n) fait que la fonction se met en veille pendant n secondes.
  • Ensuite, nous définissons un thread thread_1 avec la cible comme some_func. threading.Thread(target=…) crée un objet thread.
  • Remarque : Spécifiez le nom de la fonction et non un appel de fonction ; utilisez some_func et non some_func().
  • La création d’un objet thread ne démarre pas un thread ; appeler la méthode start() sur l’objet thread le fait.
  • Pour obtenir le nombre de threads actifs, nous utilisons la fonction active_count().

Le script Python s’exécute sur le thread principal et nous créons un autre thread (thread1) pour exécuter la fonction some_func afin que le nombre de threads actifs soit de deux, comme indiqué dans la sortie :

# Output
Running some_func...
2
Finished running some_func.

Si nous examinons de plus près la sortie, nous voyons qu’au démarrage de thread1, la première instruction d’impression s’exécute. Mais pendant l’opération de mise en veille, le processeur passe au thread principal et imprime le nombre de threads actifs, sans attendre que thread1 ait fini de s’exécuter.

Attendre que les threads terminent l’exécution

Si vous voulez que thread1 termine l’exécution, vous pouvez appeler la méthode join() dessus après avoir démarré le thread. Cela attendra que le thread1 termine son exécution sans passer au thread principal.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

Maintenant, thread1 a fini de s’exécuter avant que nous imprimions le nombre de threads actifs. Ainsi, seul le thread principal est en cours d’exécution, ce qui signifie que le nombre de threads actifs est de un. ✅

# Output
Running some_func...
Finished running some_func.
1

Comment exécuter plusieurs threads en Python

Ensuite, créons deux threads pour exécuter deux fonctions différentes.

Ici, count_down est une fonction qui prend un nombre comme argument et compte à rebours de ce nombre à zéro.

def count_down(n):
    for i in range(n,-1,-1):
        print(i)

Nous définissons count_up, une autre fonction Python qui compte de zéro jusqu’à un nombre donné.

def count_up(n):
    for i in range(n+1):
        print(i)

📑 Lors de l’utilisation de la fonction range() avec la syntaxe range(start, stop, step), l’arrêt du point final est exclu par défaut.

– Pour décompter à partir d’un nombre spécifique jusqu’à zéro, vous pouvez utiliser une valeur de pas négative de -1 et définir la valeur d’arrêt sur -1 afin que zéro soit inclus.

– De même, pour compter jusqu’à n, vous devez définir la valeur d’arrêt sur n + 1. Étant donné que les valeurs par défaut de start et step sont respectivement 0 et 1, vous pouvez utiliser range(n + 1) pour obtenir la séquence 0 à travers n.

Ensuite, nous définissons deux threads, thread1 et thread2 pour exécuter les fonctions count_down et count_up, respectivement. Nous ajoutons des instructions d’impression et des opérations de veille pour les deux fonctions.

Lors de la création des objets thread, notez que les arguments de la fonction cible doivent être spécifiés sous la forme d’un tuple, dans le paramètre args. Comme les deux fonctions (count_down et count_up) prennent un seul argument. Vous devrez insérer une virgule explicitement après la valeur. Cela garantit que l’argument est toujours transmis en tant que tuple, car les éléments suivants sont déduits comme étant Aucun.

import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Running thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Running thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

Dans la sortie :

  • La fonction count_up s’exécute sur thread2 et compte jusqu’à 5 à partir de 0.
  • La fonction count_down s’exécute sur thread1 compte à rebours de 10 à 0.
# Output
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0

Vous pouvez voir que thread1 et thread2 s’exécutent alternativement, car les deux impliquent une opération d’attente (veille). Une fois que la fonction count_up a fini de compter jusqu’à 5, thread2 n’est plus actif. Nous obtenons donc la sortie correspondant uniquement à thread1.

Résumé

Dans ce didacticiel, vous avez appris à utiliser le module de threading intégré de Python pour implémenter le multithreading. Voici un résumé des principaux plats à emporter :

  • Le constructeur Thread peut être utilisé pour créer un objet thread. L’utilisation de threading.Thread(target=,args=()) crée un thread qui exécute la cible callable avec les arguments spécifiés dans args.
  • Le programme Python s’exécute sur un thread principal, de sorte que les objets thread que vous créez sont des threads supplémentaires. Vous pouvez appeler la fonction active_count() qui renvoie le nombre de threads actifs à n’importe quelle instance.
  • Vous pouvez démarrer un thread à l’aide de la méthode start() sur l’objet thread et attendre la fin de son exécution à l’aide de la méthode join().

Vous pouvez coder des exemples supplémentaires en modifiant les temps d’attente, en essayant une opération d’E/S différente, etc. Assurez-vous d’implémenter le multithreading dans vos prochains projets Python. Bon codage !🎉