Introduction à la programmation parallèle et simultanée en Python

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:

  • Pourquoi le parallélisme est-il délicat en Python? (Indice: c'est à cause du verrou d'interprète global GIL).
  • Threads vs. Processus: Différentes façons de réaliser le parallélisme. Quand utiliser l'un sur l'autre?
  • Parallèle ou simultané: Pourquoi, dans certains cas, nous pouvons nous contenter de concurrence mais plutôt de parallélisme.
  • Construire un simple mais exemple pratique utilisant les différentes techniques discutées.

Verrou d'interprète global

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.

Threads vs. Processus

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

Parallèle ou simultané

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é..

L'écosystème de programmation parallèle / simultané de Python

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.

Construire une application pratique

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:

  • L'application passe très souvent sur une liste d'URL de sites Web et vérifie si ces sites sont actifs.
  • Chaque site Web doit être vérifié toutes les 5 à 10 minutes pour éviter les temps d'arrêt importants..
  • Au lieu d'effectuer une requête HTTP GET classique, il effectue une requête HEAD afin que cela n'affecte pas votre trafic de manière significative..
  • Si le statut HTTP est dans les plages de danger (400+, 500+), le propriétaire est averti.
  • Le propriétaire est averti par courrier électronique, message texte ou notification push.

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.

Approche en série

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

Approche de filetage

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

concurrent.futures

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 

L'approche du multitraitement

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

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

Céleri

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é..

Conclusions

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:

  • Il existe plusieurs paradigmes qui nous aident à atteindre des performances informatiques élevées en Python..
  • Pour le paradigme multithread, nous avons le filetage et concurrent.futures les bibliothèques.
  • multitraitement fournit une interface très similaire à filetage mais pour les processus plutôt que les threads.
  • Rappelez-vous que les processus réalisent un vrai parallélisme, mais qu'ils sont plus coûteux à créer.
  • Rappelez-vous qu'un processus peut contenir plus de threads.
  • Ne confondez pas parallèle avec concurrent. N'oubliez pas que seule l'approche parallèle tire parti des processeurs multicœurs, alors que la programmation simultanée planifie intelligemment les tâches de manière à ce que l'attente des opérations de longue durée soit effectuée tout en effectuant des calculs en parallèle..

Apprendre le python

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