Python est l’un des langages les plus populaires pour le traitement et la science des données en général. L'écosystème fournit un grand nombre de bibliothèques et de cadres facilitant le calcul haute performance. Faire de la programmation parallèle en Python peut s'avérer assez délicat, bien que.
Dans ce tutoriel, nous allons étudier pourquoi le parallélisme est difficile, en particulier dans le contexte Python, et pour cela, nous allons passer par ce qui suit:
le Verrou d'interprète global (GIL) est l’un des sujets les plus controversés du monde Python. Dans CPython, l’implémentation la plus répandue de Python, GIL est un mutex qui sécurise les threads. GIL facilite l'intégration avec des bibliothèques externes qui ne sont pas sécurisées pour les threads et rend le code non parallèle plus rapide. Cela a un coût, cependant. En raison du GIL, nous ne pouvons pas réaliser un véritable parallélisme via le multithreading. Fondamentalement, deux threads natifs différents du même processus ne peuvent pas exécuter le code Python à la fois.
Les choses ne sont pas si mauvaises, cependant, et voici pourquoi: tout ce qui se passe en dehors du royaume GIL est libre d'être parallèle. Dans cette catégorie, tombent des tâches longues comme les E / S et, heureusement, des bibliothèques comme numpy
.
Donc, Python n'est pas vraiment multithread. Mais qu'est-ce qu'un fil? Faisons un pas en arrière et regardons les choses en perspective.
Un processus est une abstraction de base du système d'exploitation. C'est un programme en cours d'exécution, autrement dit du code en cours d'exécution. Plusieurs processus sont toujours en cours d'exécution sur un ordinateur et ils s'exécutent en parallèle.
Un processus peut avoir plusieurs threads. Ils exécutent le même code appartenant au processus parent. Idéalement, ils fonctionnent en parallèle, mais pas nécessairement. La raison pour laquelle les processus ne sont pas suffisants est que les applications doivent être réactives et à l'écoute des actions de l'utilisateur lors de la mise à jour de l'affichage et de l'enregistrement d'un fichier..
Si cela n'est pas encore clair, voici un aide-mémoire:
PROCESSUS | FILS |
---|---|
Les processus ne partagent pas la mémoire | Les threads partagent la mémoire |
Les processus de frai / commutation sont coûteux | Frapper / changer de fil est moins cher |
Les processus nécessitent plus de ressources | Les threads nécessitent moins de ressources (parfois appelés processus légers) |
Aucune synchronisation de mémoire nécessaire | Vous devez utiliser des mécanismes de synchronisation pour vous assurer de gérer correctement les données. |
Il n'y a pas une recette qui accueille tout. Le choix dépend beaucoup du contexte et de la tâche que vous essayez d'accomplir..
Nous allons maintenant aller un peu plus loin et plonger dans la concurrence. La concurrence est souvent mal comprise et confondue avec le parallélisme. Ce n'est pas le cas. La simultanéité implique que le code indépendant de la planification soit exécuté de manière coopérative. Tirez parti du fait qu'un morceau de code est en attente d'opérations d'E / S et exécutez pendant ce temps une partie différente mais indépendante du code..
En Python, nous pouvons obtenir un comportement simultané léger via des greenlets. Du point de vue de la parallélisation, l’utilisation de threads ou de greenlets est équivalente car aucun d’eux ne fonctionne en parallèle. Les Greenlets sont encore moins coûteux à créer que les threads. De ce fait, les greenlets sont fortement utilisés pour effectuer un grand nombre de tâches d'E / S simples, comme celles que l'on trouve généralement dans les réseaux et les serveurs Web..
Maintenant que nous connaissons la différence entre les threads et les processus, parallèle et simultané, nous pouvons illustrer comment différentes tâches sont effectuées sur les deux paradigmes. Voici ce que nous allons faire: nous exécuterons plusieurs fois une tâche en dehors de GIL et une autre à l'intérieur. Nous les exécutons en série, en utilisant des threads et des processus. Définissons les tâches:
import os temps importation threads importation multitraitement NUM_WORKERS = 4 def only_sleep (): "" "Ne rien faire, attendez l'expiration du temporisateur" "" print ("PID:% s, Nom du processus:% s, Nom du fil:% s "% (os.getpid (), multiprocessing.current_process (). name, threading.current_thread (). name)) time.sleep (1) def crunch_numbers ():" "" Faire quelques calculs "" "print (" PID :% s, Nom du processus:% s, Nom du fil:% s "% (os.getpid (), multi-traitement.current_process (). name, threading.current_thread (). name)) x = 0 tant que x < 10000000: x += 1
Nous avons créé deux tâches. Les deux d'entre eux durent longtemps, mais seulement nombre de crunch
effectue activement des calculs. Courons seulement_sommeil
en série, multithread et en utilisant plusieurs processus et comparez les résultats:
## Exécuter les tâches en série start_time = time.time () pour _ dans la plage (NUM_WORKERS): only_sleep () end_time = time.time () print ("Serial time =", end_time - start_time) # Exécuter des tâches à l'aide de threads start_time = time .time () threads = [threading.Thread (target = only_sleep) pour _ dans la plage (NUM_WORKERS)] [thread.start () pour le thread dans les threads] [thread.join () pour le thread dans les threads] end_time = time.time () print ("Threads time =", end_time - start_time) # Exécuter des tâches à l'aide de processus start_time = time.time () process = [multiprocessing.Process (target = only_sleep ()) pour _ dans la plage (NUM_WORKERS)] [process. start () pour le traitement dans les processus] [process.join () pour le traitement des processus] end_time = time.time () print ("Parallel time =", end_time - start_time)
Voici le résultat que j'ai obtenu (le vôtre devrait être similaire, bien que les PID et les temps puissent varier un peu):
PID: 95726, Nom du processus: MainProcess, Nom du fil: MainThread PID: 95726, Nom du processus: MainProcess, Nom du fil: MainThread PID: 95726, Nom du processus: MainProcess, Nom du processus: MainThread PID: 95726, Nom du processus: MainPread, Nom du fil : MainThread Temps de série = 4.018089056015015 PID: 95726, Nom du processus: MainProcess, Nom du fil: PID Fil-1: 95726, Nom du processus: MainProcess, Nom du fil: PID Fil-2: 95726, Nom du processus: MainProcess, Nom du fil: Thread- 3 PID: 95726, Nom du processus: MainProcess, Nom du fil: Thread-4 temps des threads = 1.0047411918640137 PID: 95728, Nom du processus: Process-1, Nom du fil: MainThread PID: 95729, Nom du processus: Process-2, Nom du fil: MainThread PID: 95730, Nom du processus: Process-3, Nom du filetage: MainThread PID: 95731, Nom du processus: Process-4, Nom du filetage: MainThread Parallel time = 1.014023780822754.
Voici quelques observations:
Dans le cas du approche en série, les choses sont assez évidentes. Nous exécutons les tâches les unes après les autres. Les quatre exécutions sont exécutées par le même thread du même processus.
Utiliser des processus nous avons réduit le temps d'exécution au quart du temps initial, simplement parce que les tâches sont exécutées en parallèle. Notez que chaque tâche est exécutée dans un processus différent et sur le MainThread
de ce processus.
Utiliser des threads nous profitons du fait que les tâches peuvent être exécutées simultanément. Le temps d'exécution est également réduit au quart, même si rien ne se passe en parallèle. Voici comment cela se passe: nous créons le premier thread et il commence à attendre que le minuteur expire. Nous suspendons son exécution, le laissant attendre l'expiration du temporisateur, et pendant ce temps, nous générons le deuxième thread. Nous répétons cela pour tous les fils. À un moment donné, la minuterie du premier thread expire, nous passons donc à l'exécution et la terminons. L'algorithme est répété pour le second et pour tous les autres threads. À la fin, le résultat est comme si les choses se déroulaient en parallèle. Vous remarquerez également que les quatre threads différents dérivent et vivent dans le même processus: MainProcess
.
Vous remarquerez peut-être même que l'approche par thread est plus rapide que l'approche réellement parallèle. Cela est dû aux frais généraux des processus de frai. Comme nous l'avons noté précédemment, les processus de ponte et de commutation sont coûteux.
Faisons la même routine, mais cette fois en exécutant le nombre de crunch
tâche:
start_time = time.time () pour _ dans l'intervalle (NUM_WORKERS): crunch_numbers () end_time = time.time () print ("Serial time =", end_time - start_time) start_time = time.time () threads = [threading.Thread (target = crunch_numbers) pour _ dans la plage (NUM_WORKERS)] [thread.start () pour le thread dans les threads] [thread.join () pour le thread dans les threads] end_time = time.time () print ("Threads time =", end_time - start_time) start_time = time.time () process = [multitraitement.Process (target = nombre-croissants) pour _ dans la plage (NUM_WORKERS)] [process.start () pour le traitement dans les processus] [process.join () pour le traitement dans processus] heure_fin = heure.heure () print ("heure parallèle =", heure_fin - heure_début)
Voici le résultat que j'ai:
PID: 96285, Nom du processus: MainProcess, Nom du fil: MainThread PID: 96285, Nom du processus: MainProcess, Nom du fil: MainThread PID: 96285, Nom du processus: MainProcess, Nom du fil: MainThread PID: 96285, Nom du processus: MainPread, Nom du fil : MainThread Temps de série = 2.705625057220459 PID: 96285, Nom du processus: MainProcess, Nom du fil: PID Fil-1: 96285, Nom du processus: MainProcess, Nom du fil: PID Fil-2: 96285, Nom du processus: MainProcess, Nom du fil: Thread- 3 PID: 96285, Nom du processus: MainProcess, Nom du fil: Thread-4 temps des threads = 2.6961309909820557 PID: 96289, Nom du processus: Process-1, Nom du fil: MainThread PID: 96290, Nom du processus: Process-2, Nom du fil: MainThread PID: 96291, Nom du processus: Process-3, Nom du fil: MainThread PID: 96292, Nom du processus: Process-4, Nom du fil: MainThread Temps parallèle = 0.8014059066772461
La principale différence réside ici dans le résultat de l'approche multithread. Cette fois, il fonctionne de manière très similaire à l’approche série, et voici pourquoi: comme il effectue des calculs et que Python n’exécute pas un véritable parallélisme, les threads s’exécutent les uns après les autres, ce qui permet une exécution mutuelle jusqu’à ce qu’ils aient tous terminé..
Python possède des API riches pour la programmation parallèle / simultanée. Dans ce tutoriel, nous couvrons les plus populaires, mais vous devez savoir que pour répondre à tous vos besoins dans ce domaine, il existe probablement déjà quelque chose qui peut vous aider à atteindre votre objectif..
Dans la section suivante, nous construirons une application pratique sous de nombreuses formes, en utilisant toutes les bibliothèques présentées. Sans plus tarder, voici les modules / bibliothèques que nous allons couvrir:
filetage
: La manière standard de travailler avec les threads en Python. C’est un wrapper d’API de niveau supérieur recouvrant la fonctionnalité exposée par le _fil
module, qui est une interface de bas niveau sur l'implémentation de thread du système d'exploitation.
concurrent.futures
: Un module de la bibliothèque standard qui fournit une couche d’abstraction de niveau encore plus élevé sur les threads. Les threads sont modélisés comme des tâches asynchrones.
multitraitement
: Semblable à la filetage
module, offrant une interface très similaire mais utilisant des processus au lieu de threads.
gevent et greenlets
: Les Greenlets, également appelés micro-threads, sont des unités d'exécution qui peuvent être planifiées en collaboration et peuvent effectuer des tâches simultanément sans trop de charge.
céleri
: Une file d'attente de tâches distribuée de haut niveau. Les tâches sont mises en file d'attente et exécutées simultanément à l'aide de divers paradigmes tels que multitraitement
ou gevent
.
Connaître la théorie, c'est bien, mais la meilleure façon d'apprendre est de construire quelque chose de concret, non? Dans cette section, nous allons construire un type d’application classique en passant par tous les paradigmes.
Construisons une application qui vérifie la disponibilité des sites Web. Il existe de nombreuses solutions de ce type, les plus connues étant probablement Jetpack Monitor et Uptime Robot. Le but de ces applications est de vous avertir lorsque votre site Web est en panne afin que vous puissiez agir rapidement. Voici comment ils fonctionnent:
Voici pourquoi il est essentiel d’adopter une approche parallèle / simultanée au problème. À mesure que la liste des sites Web s'allonge, parcourir cette liste en série ne nous garantit pas que tous les sites Web sont vérifiés toutes les cinq minutes environ. Les sites Web peuvent être en panne pendant des heures et le propriétaire ne sera pas averti.
Commençons par écrire quelques utilitaires:
# utils.py heure d'importation importation journalisation des demandes d'importation classe WebsiteDownException (Exception): passer def ping_website (address, timeout = 20): "" "Vérifiez si un site Web est en panne. Un site Web est considéré en panne si le code d'état>> 400 ou si le délai expire, émettez une exception WebsiteDownException si l'une des conditions d'arrêt du site Web est remplie "" "essayez: response = requests.head (adresse, délai d'attente = délai d'attente) si code_répertoire_réponse> = 400: logging.warning (" Site Web% s renvoyé status_code =% s "% (adresse, code_état_réaction)) lève une exception WebsiteDownException () sauf requests.exceptions.RequestException: logging.warning (" Le délai d'attente a expiré pour le site Web% s "% adresse) une élève WebsiteDownException () def notify_owner (address): "" "Envoyer au propriétaire de l'adresse une notification l'informant que son site Web est en panne Pour le moment, nous ne nous sommes endormis que pendant 0,5 seconde, mais c'est ici que vous enverriez un courrier électronique, une notification push ou un enregistrement de message texte" "". info ("Notifier le propriétaire du site Web% s"% adresse) time.sleep (0.5) def check_webs ite (adresse): "" "Fonction utilitaire: vérifie si un site Web est en panne; si tel est le cas, avertissez l'utilisateur" "" essayez: ping_website (adresse) sauf WebsiteDownException: notify_owner (adresse)
Nous aurons en fait besoin d’une liste de sites Web pour essayer notre système. Créez votre propre liste ou utilisez la mienne:
# websites.py WEBSITE_LIST = ['http://envato.com', 'http://amazon.co.uk', 'http://amazon.com', 'http://facebook.com', ' http://google.com ',' http://google.fr ',' http://google.es ',' http://google.co.uk ',' http://internet.org ' , 'http://gmail.com', 'http://stackoverflow.com', 'http://github.com', 'http://heroku.com', 'http: // vraiment-cool- disponible-domain.com ',' http://djangoproject.com ',' http://rubyonrails.org ',' http://basecamp.com ',' http://trello.com ',' http: //yiiframework.com ',' http://shopify.com ',' http://another-really- interesting-domain.co ',' http://airbnb.com ',' http: // instagram. com ',' http://snClin.com ',' http://youtube.com ',' http://baidu.com ',' http://yahoo.com ',' http: // live. com ',' http://linkedin.com ',' http://yandex.ru ',' http://netflix.com ',' http://wordpress.com ',' http: // bing. com ']
Normalement, vous garderiez cette liste dans une base de données avec les informations de contact du propriétaire afin de pouvoir les contacter. Comme ce n’est pas le sujet principal de ce tutoriel, et par souci de simplicité, nous allons simplement utiliser cette liste Python..
Si vous avez prêté une très bonne attention, vous avez peut-être remarqué deux domaines très longs dans la liste qui ne sont pas des sites Web valides (j'espère que personne ne les aura achetés au moment où vous lisez ceci pour me prouver le contraire!). J'ai ajouté ces deux domaines pour être sûr d'avoir des sites Web à chaque passage. Aussi, nommons notre application UptimeSquirrel.
Tout d’abord, essayons l’approche en série et voyons à quel point elle fonctionne. Nous considérerons cela comme la base.
# serial_squirrel.py heure d’importation start_time = time.time () pour l’adresse dans WEBSITE_LIST: check_website (adresse) end_time = time.time () print ("Heure de SerialSquirrel:% ssecs"% (end_time - start_time)) # WARNING: root : Délai d'attente expiré pour le site Web http://really-cool-available-domain.com # AVERTISSEMENT: root: délai d'attente expiré pour le site Web http://another-really-interesting-domain.co # AVERTISSEMENT: root: site Web http: // bing.com a renvoyé status_code = 405 # Heure de SerialSquirrel: 15.881232261657715secs
Nous allons être un peu plus créatifs avec la mise en œuvre de l'approche par thread. Nous utilisons une file d'attente pour insérer les adresses et créer des threads de travail pour les extraire de la file d'attente et les traiter. Nous allons attendre que la file soit vide, ce qui signifie que toutes les adresses ont été traitées par nos threads de travail..
# threaded_squirrel.py heure d'importation de la file d'attente d'importation File d'attente NUM_WORKERS = 4 task_queue = Queue () def worker (): # Recherchez constamment des adresses dans la file d'attente pendant que True: address = task_queue.get () check_website (address) # Mark la tâche traitée comme étant task_queue.task_done () start_time = time.time () # Créez les threads de travail threads = [Thread (cible = worker) pour _ dans la plage (NUM_WORKERS)] # Ajoutez les sites Web à la file d'attente des tâches [task_queue. put (item) pour item dans WEBSITE_LIST] # # Démarre tous les travailleurs [thread.start () pour thread dans les threads] # Attend le traitement de toutes les tâches de la file d'attente task_queue.join () end_time = time.time () print ("Time for ThreadedSquirrel:% ssecs"% (end_time - start_time)) # AVERTISSEMENT: root: le délai d'expiration a expiré pour le site Web http://really-cool-available-domain.com # AVERTISSEMENT: root: le délai d'expiration a été dépassé pour le site Web http: / /another-really-interesting-domain.co # AVERTISSEMENT: root: Le site Web http://bing.com a renvoyé status_code = 405 # Heure pour ThreadedSquirrel: 3.1107530 59387207secs
Comme indiqué précédemment, concurrent.futures
est une API de haut niveau pour l'utilisation de threads. L’approche que nous adoptons ici implique l’utilisation d’un ThreadPoolExecutor
. Nous allons soumettre des tâches au pool et récupérer des contrats à terme, résultats qui seront disponibles à l'avenir. Bien sûr, nous pouvons attendre que tous les futurs deviennent des résultats concrets.
# future_squirrel.py heure d'importation import concurrent.futures NUM_WORKERS = 4 start_time = time.time () avec concurrent.futures.ThreadPoolExecutor (max_workers = NUM_WORKERS) en tant qu'exécuteur: futures = executor.submit (check_website, adresse) pour l'adresse dans WEITEITE_LIST concurrent.futures.wait (futures) end_time = time.time () print ("Time for FutureSquirrel:% ssecs"% (end_time - start_time)) # AVERTISSEMENT: root: le délai d'attente a expiré pour le site Web http: // vraiment-cool-available -domain.com # AVERTISSEMENT: root: le délai d'attente a expiré pour le site Web http://another-really-interesting-domain.co # AVERTISSEMENT: root: le site Web http://bing.com a renvoyé status_code = 405 # Heure pour FutureSquirrel: 1.812899112701416secs
le multitraitement
La bibliothèque fournit une API de remplacement presque instantanée pour le filetage
bibliothèque. Dans ce cas, nous allons adopter une approche plus semblable à la concurrent.futures
un. Nous mettons en place un multitraitement.Pool
et lui soumettre des tâches en mappant une fonction à la liste d’adresses (pensez au classique Python carte
une fonction).
# multiprocessing_squirrel.py heure d'importation importation socket importation multitraitement NUM_WORKERS = 4 start_time = time.time () avec multiprocessing.Pool (processus = NUM_WORKERS) en tant que pool: results = pool.map_async (check_website, WEBSITE_LIST) results.wait () end_time = time .time () print ("Time for MultiProcessingSquirrel:% ssecs"% (end_time - start_time)) # AVERTISSEMENT: root: délai d’expiration écoulé pour le site Web http://really-cool-available-domain.com # AVERTISSEMENT: root: délai d’expiration écoulé pour le site Web http://another-really-interesting-domain.co # AVERTISSEMENT: root: Le site Web http://bing.com a renvoyé status_code = 405 # Heure du traitement multiprocesseurSquirrel: 2.8224599361419678secs
Gevent est une alternative populaire pour obtenir une simultanéité massive. Il y a quelques choses que vous devez savoir avant de l'utiliser:
Le code exécuté simultanément par les greenlets est déterministe. Contrairement aux autres alternatives présentées, ce paradigme garantit que pour deux exécutions identiques, vous obtiendrez toujours les mêmes résultats dans le même ordre..
Vous devez assigner des fonctions standard au singe pour qu'elles coopèrent avec gevent. Voici ce que je veux dire par là. Normalement, une opération de socket bloque. Nous attendons que l'opération se termine. Si nous étions dans un environnement multithread, le planificateur basculerait simplement sur un autre thread pendant que l'autre attend des E / S. Puisque nous ne sommes pas dans un environnement multithread, gevent corrige les fonctions standard pour qu’elles deviennent non bloquantes et renvoient le contrôle à l’ordonnanceur gevent..
Pour installer gevent, lancez: pip installer gevent
Voici comment utiliser gevent pour effectuer notre tâche en utilisant un gevent.pool.Pool
:
# green_squirrel.py heure d'importation à partir de gevent.pool pool d'importation à partir de gevent import monkey # Notez que vous pouvez générer de nombreux travailleurs avec gevent car le coût de création et de commutation est très faible. NUM_WORKERS = 4 # Module de socket Monkey-Patch pour les requêtes HTTP monkey. patch_socket () start_time = time.time () pool = Pool (NUM_WORKERS) pour l'adresse dans WEBSITE_LIST: pool.spawn (check_website, address) # Attend la fin de la tâche pool.join () end_time = time.time () print (" Heure de GreenSquirrel:% ssecs "% (heure_fin - heure_début)) # Heure de GreenSquirrel: 3.8395519256591797secs
Le céleri est une approche qui diffère principalement de ce que nous avons vu jusqu'à présent. Il est testé sur le terrain dans des environnements très complexes et très performants. L'installation de céleri nécessitera un peu plus de bricolage que toutes les solutions ci-dessus.
Premièrement, nous devrons installer le céleri:
pip installer le céleri
Les tâches sont les concepts centraux du projet Celery. Tout ce que vous voudrez utiliser à l'intérieur du céleri doit être une tâche. Celery offre une grande flexibilité pour l'exécution de tâches: vous pouvez les exécuter de manière synchrone ou asynchrone, en temps réel ou planifié, sur le même ordinateur ou sur plusieurs ordinateurs, et à l'aide de threads, processus, Eventlet ou gevent..
L'arrangement sera légèrement plus complexe. Le céleri utilise d'autres services pour envoyer et recevoir des messages. Ces messages sont généralement des tâches ou des résultats de tâches. Nous allons utiliser Redis dans ce tutoriel à cette fin. Redis est un excellent choix car il est très facile à installer et à configurer et vous pouvez déjà l’utiliser dans votre application à d’autres fins, telles que la mise en cache et les fonctions pub / sub..
Vous pouvez installer Redis en suivant les instructions de la page Redis Quick Start. N'oubliez pas d'installer le redis
Librairie Python, pip installer redis
, et le paquet nécessaire à l’utilisation de Redis et Céleri: pip installer céleri [redis]
.
Démarrez le serveur Redis comme ceci: $ redis-server
Pour commencer à créer des éléments avec Celery, nous devons d’abord créer une application Celery. Après cela, le céleri a besoin de savoir quel type de tâches il peut exécuter. Pour ce faire, nous devons enregistrer les tâches dans l'application Celery. Nous ferons cela en utilisant le @ app.task
décorateur:
# celery_squirrel.py heure d'importation depuis utils import check_website depuis importation de données WEBSITE_LIST depuis import céleri : // localhost: 6379/0 ") @ app.task def check_website_task (adresse): renvoie check_website (adresse) si __name__ ==" __main__ ": start_time = time.time () # L'utilisation de 'delay' exécute la tâche asynchrone = ResultSet ([check_website_task.delay (adresse) pour l'adresse dans WEBSITE_LIST]) # Attendez la fin des tâches rs.get () end_time = time.time () print ("CelerySquirrel:", end_time - start_time) # CelerySquirrel: 2.4979639053344727
Ne paniquez pas si rien ne se passe. Rappelez-vous, le céleri est un service et nous devons l'exécuter. Jusqu'à présent, nous n'avions placé que les tâches dans Redis, mais nous n'avions pas encore démarré le céleri pour les exécuter. Pour ce faire, nous devons exécuter cette commande dans le dossier où réside notre code:
travailleur de céleri -A do_celery --loglevel = debug --concurrency = 4
Maintenant, réexécutez le script Python et voyez ce qui se passe. Une chose à laquelle il faut prêter attention: remarquez comment nous avons passé l'adresse deux fois à notre demande Redis. le courtier
paramètre spécifie où les tâches sont passées à Celery, et backend
est où Celery met les résultats afin que nous puissions les utiliser dans notre application. Si nous ne spécifions pas de résultat backend
, il est impossible pour nous de savoir quand la tâche a été traitée et quel en a été le résultat.
Sachez également que les journaux se trouvent maintenant dans la sortie standard du processus Celery. Veillez donc à les extraire dans le terminal approprié..
J'espère que cela a été un voyage intéressant pour vous et une bonne introduction au monde de la programmation parallèle / simultanée en Python. C'est la fin du voyage et nous pouvons tirer certaines conclusions:
filetage
et concurrent.futures
les bibliothèques.multitraitement
fournit une interface très similaire à filetage
mais pour les processus plutôt que les threads.Apprenez Python avec notre guide complet de tutoriel sur Python, que vous soyez débutant ou que vous soyez un codeur chevronné cherchant à acquérir de nouvelles compétences..