Inleiding tot parallelle en gelijktijdige programmering in Python

Python is een van de meest populaire talen voor gegevensverwerking en gegevenswetenschap in het algemeen. Het ecosysteem biedt veel bibliotheken en frameworks die high-performance computing mogelijk maken. Parallel programmeren in Python kan echter behoorlijk lastig zijn.

In deze zelfstudie gaan we bestuderen waarom parallellisme moeilijk is, vooral in de Python-context, en daarvoor zullen we het volgende doornemen:

  • Waarom is parallellisme lastig in Python (hint: het komt door de GIL - de mondiale interpreter lock).
  • Threads vs. Processes: Verschillende manieren om parallellisme te bereiken. Wanneer de ene boven de andere gebruiken?
  • Parallel versus gelijklopend: Waarom kunnen we in sommige gevallen genoegen nemen met concurrency in plaats van parallellisme.
  • Bouwen aan een eenvoudig maar praktisch voorbeeld met behulp van de verschillende besproken technieken.

Global Interpreter Lock

De Global Interpreter Lock (GIL) is een van de meest controversiële onderwerpen in de Python-wereld. In CPython, de meest populaire implementatie van Python, is de GIL een mutex die dingen garenveilig maakt. De GIL maakt het eenvoudig om te integreren met externe bibliotheken die niet thread-veilig zijn, en het maakt niet-parallelle code sneller. Dit brengt echter kosten met zich mee. Vanwege de GIL kunnen we niet echt parallellisme bereiken via multithreading. In principe kunnen twee verschillende native threads van hetzelfde proces de Python-code niet tegelijk uitvoeren.

Maar de dingen zijn niet zo slecht, en dit is waarom: dingen die buiten het GIL-rijk gebeuren, zijn vrij om parallel te zijn. In deze categorie vallen langlopende taken als I / O en, gelukkig, bibliotheken leuk numpy.

Threads vs. Processes

Dus Python is niet echt multithreaded. Maar wat is een rode draad? Laten we een stap terug doen en de dingen in perspectief bekijken.

Een proces is een elementaire abstractie van het besturingssysteem. Het is een programma dat wordt uitgevoerd, met andere woorden, de code die wordt uitgevoerd. Meerdere processen worden altijd uitgevoerd op een computer en ze worden parallel uitgevoerd.

Een proces kan meerdere threads hebben. Ze voeren dezelfde code uit die bij het bovenliggende proces hoort. Idealiter lopen ze parallel, maar niet noodzakelijkerwijs. De reden waarom processen niet genoeg zijn, is omdat applicaties responsief moeten zijn en moeten luisteren naar gebruikersacties terwijl het scherm wordt bijgewerkt en een bestand wordt opgeslagen.

Als dat nog steeds een beetje onduidelijk is, is hier een cheatsheet:

PROCESSEN
THREADS
Processen delen geen geheugen
Threads delen het geheugen
Paai / schakelprocessen zijn duur
Paaien / schakelen van draden is minder duur
Processen vereisen meer middelen
Threads hebben minder middelen nodig (worden ook wel lichtgewichtprocessen genoemd)
Geen geheugensynchronisatie nodig
U moet synchronisatiemechanismen gebruiken om er zeker van te zijn dat u de gegevens correct verwerkt

Er is niet één recept dat alles accommodeert. Het kiezen van een daarvan is sterk afhankelijk van de context en de taak die u probeert te bereiken.

Parallel versus gelijklopend

Nu gaan we een stap verder en duiken we gelijktijdigheid in. Concurrency wordt vaak verkeerd begrepen en aangezien voor parallellisme. Dat is niet het geval. Concurrency houdt in dat onafhankelijke code wordt gepland om op een coöperatieve manier te worden uitgevoerd. Maak gebruik van het feit dat een stuk code wacht op I / O-bewerkingen en gedurende die tijd een ander maar onafhankelijk deel van de code uitvoert.

In Python kunnen we lichtgewicht gelijktijdig gedrag bereiken via greenlets. Vanuit parallellisatieperspectief is het gebruik van threads of greenlets equivalent omdat geen van beide parallel loopt. Greenlets zijn zelfs minder duur om te maken dan threads. Daarom worden greenlets veel gebruikt voor het uitvoeren van een groot aantal eenvoudige I / O-taken, zoals de taken die meestal worden aangetroffen in netwerken en webservers.

Nu we het verschil kennen tussen threads en processen, parallel en gelijktijdig, kunnen we illustreren hoe verschillende taken worden uitgevoerd op de twee paradigma's. Dit is wat we gaan doen: we zullen meerdere keren uitvoeren, een taak buiten de GIL en een taak erin. We voeren ze serieel uit, gebruiken threads en gebruiken processen. Laten we de taken definiëren:

import os import time import threading import multiprocessing NUM_WORKERS = 4 def only_sleep (): "" "Niets doen, wachten tot een timer verloopt" "" print ("PID:% s, Procesnaam:% s, Thread Name:% s "% (os.getpid (), multiprocessing.current_process (). name, threading.current_thread (). name)) time.sleep (1) def crunch_numbers ():" "" Voer enkele berekeningen uit "" "print (" PID :% s, Procesnaam:% s, Thread Name:% s "% (os.getpid (), multiprocessing.current_process (). name, threading.current_thread (). name)) x = 0 while x < 10000000: x += 1

We hebben twee taken gemaakt. Beide zijn langlopend, maar alleen crunch_numbers voert actief berekeningen uit. Laten we rennen alleen slapen serieel, multithreaded en met behulp van meerdere processen en vergelijk de resultaten:

## Taken serieel uitvoeren start_time = time.time () voor _ binnen bereik (NUM_WORKERS): only_sleep () end_time = time.time () print ("Serial time =", end_time - start_time) # Voer taken uit met threads start_time = time .time () threads = [threading.Thread (target = only_sleep) voor _ binnen bereik (NUM_WORKERS)] [thread.start () voor discussie in discussielijnen] [thread.join () voor discussie in discussielijnen] end_time = time.time () print ("Threads time =", end_time - start_time) # Voer taken uit met behulp van processen start_time = time.time () processes = [multiprocessing.Process (target = only_sleep ()) voor _ binnen bereik (NUM_WORKERS)] [proces. start () voor proces in processen] [process.join () voor proces in processen] end_time = time.time () print ("Parallel time =", end_time - start_time)

Dit is de uitvoer die ik heb (de jouwe zou vergelijkbaar moeten zijn, hoewel de PID's en tijden een beetje zullen variëren):

PID: 95726, Procesnaam: MainProcess, Thread Name: MainThread PID: 95726, Procesnaam: MainProcess, Thread Name: MainThread PID: 95726, Procesnaam: MainProcess, Thread Name: MainThread PID: 95726, Procesnaam: MainProcess, Thread Name : MainThread Serial time = 4.018089056015015 PID: 95726, Procesnaam: MainProcess, Thread Name: Thread-1 PID: 95726, Procesnaam: MainProcess, Thread Name: Thread-2 PID: 95726, Procesnaam: MainProcess, Thread Name: Thread- 3 PID: 95726, Procesnaam: MainProcess, Thread Name: Thread-4 Threads time = 1.0047411918640137 PID: 95728, Procesnaam: Process-1, Thread Name: MainThread PID: 95729, Procesnaam: Process-2, Thread Name: MainThread PID: 95730, Procesnaam: Proces-3, Thread Name: MainThread PID: 95731, Procesnaam: Process-4, Thread Name: MainThread Parallel time = 1.014023780822754

Hier zijn enkele opmerkingen:

  • In het geval van de seriële aanpak, dingen zijn vrij duidelijk. We voeren de taken één voor één uit. Alle vier de runs worden uitgevoerd door dezelfde thread van hetzelfde proces.

  • Processen gebruiken we hebben de uitvoeringstijd teruggebracht tot een kwart van de oorspronkelijke tijd, simpelweg omdat de taken parallel worden uitgevoerd. Merk op hoe elke taak wordt uitgevoerd in een ander proces en op de MainThread van dat proces.

  • Gebruik van threads we maken gebruik van het feit dat de taken tegelijkertijd kunnen worden uitgevoerd. De uitvoeringstijd wordt ook teruggebracht tot een kwart, hoewel er niets parallel loopt. Hier is hoe dat gaat: we spawnen de eerste thread en het begint te wachten tot de timer verloopt. We pauzeren de uitvoering en laten het wachten tot de timer afloopt, en in deze tijd spawnen we de tweede thread. We herhalen dit voor alle threads. Op een bepaald moment verloopt de timer van de eerste thread, dus we schakelen de uitvoering ervan om en we beëindigen deze. Het algoritme wordt herhaald voor de tweede en voor alle andere threads. Aan het einde is het resultaat alsof dingen parallel zijn verlopen. Je zult ook opmerken dat de vier verschillende draden vertakken en leven in hetzelfde proces: MainProcess.

  • Je merkt misschien zelfs dat de threaded approach sneller is dan de werkelijk parallelle benadering. Dat komt door de overhead van paaiprocessen. Zoals we eerder opmerkten, is paaien en schakelen een dure operatie.

Laten we dezelfde routine doen, maar deze keer loopt het crunch_numbers taak:

start_time = time.time () voor _ binnen bereik (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) voor _ binnen bereik (NUM_WORKERS)] [thread.start () voor discussie in discussielijnen] [thread.join () voor discussie in discussielijnen] end_time = time.time () print ("Threads time =", end_time - start_time) start_time = time.time () processen = [multiprocessing.Process (target = crunch_numbers) voor _ binnen bereik (NUM_WORKERS)] [process.start () voor proces in processen] [process.join () voor proces in processen] end_time = time.time () print ("Parallel time =", end_time - start_time)

Dit is de uitvoer die ik heb:

PID: 96285, Procesnaam: MainProcess, Thread Name: MainThread PID: 96285, Procesnaam: MainProcess, Thread Name: MainThread PID: 96285, Procesnaam: MainProcess, Thread Name: MainThread PID: 96285, Procesnaam: MainProcess, Thread Name : MainThread Serial time = 2.705625057220459 PID: 96285, Procesnaam: MainProcess, Thread Name: Thread-1 PID: 96285, Procesnaam: MainProcess, Thread Name: Thread-2 PID: 96285, Procesnaam: MainProcess, Thread Name: Thread- 3 PID: 96285, Procesnaam: MainProcess, Thread Name: Thread-4 Threads time = 2.6961309909820557 PID: 96289, Procesnaam: Proces-1, Thread Name: MainThread PID: 96290, Procesnaam: Process-2, Thread Name: MainThread PID: 96291, Procesnaam: Proces-3, Thread Name: MainThread PID: 96292, Procesnaam: Process-4, Thread Name: MainThread Parallel time = 0.8014059066772461

Het belangrijkste verschil is hier in het resultaat van de multithreaded aanpak. Deze keer doet het zeer vergelijkbaar met de seriële benadering, en dit is waarom: omdat het berekeningen uitvoert en Python geen echt parallellisme uitvoert, lopen de threads in feite één na één, waardoor de uitvoering aan elkaar wordt overgedragen totdat ze allemaal eindigen.

Het Python Parallel / Concurrent Programming Ecosystem

Python heeft rijke API's voor het doen van parallelle / gelijktijdige programmering. In deze zelfstudie behandelen we de meest populaire, maar je moet weten dat voor elke behoefte die je op dit gebied hebt, er waarschijnlijk al iets is dat je kan helpen je doel te bereiken. 

In de volgende sectie zullen we een praktische toepassing bouwen in vele vormen, waarbij we alle gepresenteerde bibliotheken gebruiken. Zonder verder oponthoud, hier zijn de modules / bibliotheken die we gaan behandelen:

  • threading: De standaard manier van werken met threads in Python. Het is een API-omhulsel van een hoger niveau dan de functionaliteit die wordt aangeboden door de _draad module, wat een low-level interface is over de thread-implementatie van het besturingssysteem.

  • concurrent.futures: Een modulegedeelte van de standaardbibliotheek dat een abstractielaag op een nog hoger niveau biedt dan threads. De threads worden gemodelleerd als asynchrone taken.

  • multiprocessing: Gelijk aan threading module, die een erg vergelijkbare interface biedt maar processen gebruikt in plaats van threads.

  • gevent en greenlets: Greenlets, ook wel micro-threads genoemd, zijn uitvoereenheden die gezamenlijk kunnen worden gepland en tegelijkertijd taken kunnen uitvoeren zonder veel overhead.

  • selderij: Een gestapelde taakwachtrij op hoog niveau. De taken worden in een wachtrij geplaatst en gelijktijdig uitgevoerd met behulp van verschillende paradigma's zoals multiprocessing of gevent.

Een praktische toepassing bouwen

De theorie kennen is leuk en goed, maar de beste manier om te leren is om iets praktischs te bouwen, toch? In deze sectie gaan we een klassiek type applicatie bouwen dat door alle verschillende paradigma's gaat.

Laten we een applicatie bouwen die de uptime van websites controleert. Er zijn veel van dergelijke oplossingen die er zijn, de meest bekende zijn waarschijnlijk Jetpack Monitor en Uptime Robot. Het doel van deze apps is om u op de hoogte te stellen wanneer uw website offline is, zodat u snel actie kunt ondernemen. Dit is hoe ze werken:

  • De applicatie gaat heel vaak over een lijst met website-URL's en controleert of die websites zijn opgestart.
  • Elke website moet elke 5-10 minuten worden gecontroleerd, zodat de downtime niet significant is.
  • In plaats van een klassiek HTTP GET-verzoek uit te voeren, voert het een HEAD-verzoek uit, zodat het uw verkeer niet aanzienlijk beïnvloedt.
  • Als de HTTP-status zich binnen de gevarenbereiken (400+, 500+) bevindt, wordt de eigenaar hiervan op de hoogte gebracht.
  • De eigenaar wordt per e-mail, sms of push-notificatie op de hoogte gesteld.

Dit is waarom het essentieel is om een ​​parallelle / gelijktijdige benadering van het probleem te nemen. Naarmate de lijst met websites groeit, zal het niet serieus doorlopen van de lijst ons garanderen dat elke website ongeveer elke vijf minuten wordt gecontroleerd. De websites kunnen uren leeg zijn en de eigenaar zal niet op de hoogte worden gebracht.

Laten we beginnen met het schrijven van enkele hulpprogramma's:

# utils.py importeer tijd import logging import requests class WebsiteDownException (Uitzondering): pass def ping_website (address, timeout = 20): "" "Controleer of een website down is. Een website wordt overwogen als de statuscode> = 400 of als de time-out vervalt Gooi een WebsiteDownException als aan een van de down-voorwaarden van de website is voldaan "" "try: response = requests.head (address, timeout = time-out) als response.status_code> = 400: logging.warning (" Website% s geretourneerd status_code =% s "% (adres, response.status_code)) verhogen WebsiteDownException () behalve requests.exceptions.RequestException: logging.warning (" Time-out verlopen voor website% s "% -adres) verhogen WebsiteDownException () def notify_owner (adres): "" "Stuur de eigenaar van het adres een melding dat hun website down is. Voor nu gaan we gewoon 0,5 seconden slapen, maar dit is waar je een e-mail, push-bericht of tekstbericht" "" logging zou sturen. info ("Kennisgeving aan de eigenaar van% s website"% address) time.sleep (0.5) def check_webs ite (adres): "" "Functie functie: controleer of een website down is, zo ja, stel de gebruiker op de hoogte" "" try: ping_website (address) except WebsiteDownException: notify_owner (address)

We hebben een website-lijst nodig om ons systeem uit te proberen. Maak je eigen lijst of gebruik de mijne:

# 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: // really-cool- available-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://snapchat.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 ',]

Normaal gesproken bewaart u deze lijst in een database samen met contactgegevens van de eigenaar, zodat u contact met hen kunt opnemen. Omdat dit niet het hoofdonderwerp van deze tutorial is, en omwille van de eenvoud, gebruiken we deze Python-lijst gewoon.

Als je echt veel aandacht hebt besteed, heb je misschien twee hele lange domeinen in de lijst opgemerkt die geen geldige websites zijn (ik hoop dat niemand ze heeft gekocht tegen de tijd dat je dit leest om te bewijzen dat ik ongelijk heb!). Ik heb deze twee domeinen toegevoegd om er zeker van te zijn dat we bij elke run een aantal websites hebben. Laten we ook onze app een naam geven UptimeSquirrel.

Seriële aanpak

Laten we eerst eens kijken naar de seriële benadering en zien hoe slecht deze presteert. We beschouwen dit als de basislijn.

# serial_squirrel.py import time start_time = time.time () voor adres in WEBSITE_LIST: check_website (adres) end_time = time.time () print ("Time for SerialSquirrel:% ssecs"% (end_time - start_time)) # WAARSCHUWING: root : Time-out verlopen voor website http://really-cool-available-domain.com # WAARSCHUWING: root: Time-out verlopen voor website http://another-really-interesting-domain.co # WAARSCHUWING: root: Website http: // bing.com is terug status_code = 405 # Tijd voor SerialSquirrel: 15.881232261657715secs

Threading Approach

We gaan een beetje creatiever worden met de implementatie van de threaded-aanpak. We gebruiken een wachtrij om de adressen in te voegen en werkthreads te maken om ze uit de wachtrij te halen en te verwerken. We wachten tot de wachtrij leeg is, wat betekent dat alle adressen zijn verwerkt door de threads van onze medewerkers.

# threaded_squirrel.py import time from queue import Wachtrij van threading import Thread NUM_WORKERS = 4 task_queue = Wachtrij () def worker (): # Controleer de wachtrij voortdurend op adressen terwijl True: address = task_queue.get () check_website (address) # Mark de verwerkte taak als voltooid task_queue.task_done () start_time = time.time () # Maak de worker-threads threads = [Thread (target = worker) for _ in range (NUM_WORKERS)] # Voeg de websites toe aan de takenwachtrij [task_queue. zet (item) voor item in WEBSITE_LIST] # Start alle medewerkers [thread.start () voor thread in discussies] # Wacht tot alle taken in de wachtrij zijn verwerkt task_queue.join () end_time = time.time () print ("Tijd voor ThreadedSquirrel:% ssecs"% (eind_tijd - start_tijd)) # WAARSCHUWING: root: Time-out verlopen voor website http://really-cool-available-domain.com # WAARSCHUWING: root: Time-out verlopen voor website http: / / another -really -interesting-domain.co # WARNING: root: Website http://bing.com teruggestuurd status_code = 405 # Tijd voor ThreadedSquirrel: 3.1107530 59387207secs

concurrent.futures

Zoals eerder vermeld, concurrent.futures is een high-level API voor het gebruik van threads. De aanpak die we hier volgen impliceert het gebruik van a ThreadPoolExecutor. We zullen taken bij de pool indienen en de toekomst terugkrijgen, wat resultaten zijn die we in de toekomst beschikbaar zullen hebben. Natuurlijk kunnen we wachten totdat alle toekomstige resultaten daadwerkelijk zijn.

# future_squirrel.py import tijd gelijktijdig.futures importeren NUM_WORKERS = 4 start_time = time.time () met concurrent.futures.ThreadPoolExecutor (max_workers = NUM_WORKERS) als uitvoerder: futures = executor.submit (check_website, adres) voor adres in WEBSITE_LIST concurrent.futures.wait (futures) end_time = time.time () print ("Time for FutureSquirrel:% ssecs"% (end_time - start_time)) # WARNING: root: Time-out verlopen voor website http: // really-cool-available -domein.com # WAARSCHUWING: root: Time-out verlopen voor website http://another-really-interesting-domain.co # WAARSCHUWING: root: Website http://bing.com terug status_code = 405 # Tijd voor FutureSquirrel: 1.812899112701416secs 

De multiprocessing-aanpak

De multiprocessing library biedt een bijna drop-in vervangende API voor de threading bibliotheek. In dit geval gaan we een benadering nemen die meer lijkt op de concurrent.futures een. We zijn aan het opzetten multiprocessing.Pool en het verzenden van taken naar het door een functie toewijzen aan de lijst met adressen (denk aan de klassieke Python kaart functie).

# multiprocessing_squirrel.py importeer importeer import socket multiprocessing NUM_WORKERS = 4 start_time = time.time () met multiprocessing.Pool (processen = NUM_WORKERS) als pool: resultaten = pool.map_async (check_website, WEBSITE_LIST) results.wait () end_time = time .time () print ("Tijd voor MultiProcessingSquirrel:% ssecs"% (end_time - start_time)) # WAARSCHUWING: root: Time-out verlopen voor website http://really-cool-available-domain.com # WAARSCHUWING: root: Time-out verlopen voor website http://another-really-interesting-domain.co # WAARSCHUWING: root: Website http://bing.com terug status_code = 405 # Tijd voor MultiProcessingSquirrel: 2.8224599361419678secs

Gevent

Gevent is een populair alternatief voor het bereiken van massale concurrency. Er zijn een paar dingen die je moet weten voordat je het gebruikt:

  • De code die gelijktijdig door greenlets wordt uitgevoerd, is deterministisch. In tegenstelling tot de andere gepresenteerde alternatieven, garandeert dit paradigma dat voor elke twee identieke runs, u altijd dezelfde resultaten krijgt in dezelfde volgorde.

  • Je moet standaardfuncties van de aap aaien, zodat ze samenwerken met gevent. Dit is wat ik daarmee bedoel. Normaal gesproken blokkeert een socketbewerking. We wachten tot de bewerking is voltooid. Als we ons in een multithreaded omgeving bevonden, zou de scheduler eenvoudig overschakelen naar een andere thread terwijl de andere wacht op I / O. Omdat we ons niet in een multithread-omgeving bevinden, pattert gevent de standaardfuncties zodat ze niet-blokkerend worden en de controle teruggeven aan de planner van gevent.

Om gevent te installeren, voer je uit: pip install gevent

Hier leest u hoe u gevent gebruikt om onze taak uit te voeren met a gevent.pool.Pool:

# green_squirrel.py importeert tijd van gevent.pool import Pool van aap import aap # Merk op dat je veel werknemers kunt paaien met gevent omdat de kosten van maken en schakelen erg laag is NUM_WORKERS = 4 # Monkey-Patch-socketmodule voor HTTP-verzoeken monkey. patch_socket () start_time = time.time () pool = Pool (NUM_WORKERS) voor adres in WEBSITE_LIST: pool.spawn (check_website, adres) # Wacht tot dingen eindigen pool.join () end_time = time.time () print (" Tijd voor GreenSquirrel:% ssecs "% (end_time - start_time)) # Tijd voor GreenSquirrel: 3.8395519256591797secs

Selderij

Selderij is een aanpak die meestal verschilt van wat we tot nu toe hebben gezien. Het is gevechtstest in de context van zeer complexe en krachtige omgevingen. Het opzetten van Celery vereist een beetje meer knutselen dan alle bovenstaande oplossingen.

Eerst moeten we Celery installeren:

pip installeer selderij

Taken zijn de centrale concepten binnen het Celery-project. Alles dat je in Celery wilt laten draaien, moet een taak zijn. Celery biedt grote flexibiliteit voor het uitvoeren van taken: u kunt ze synchroon of asynchroon, realtime of gepland uitvoeren, op dezelfde machine of op meerdere machines en met behulp van threads, processen, Eventlet of Gevent.

Het arrangement zal iets complexer zijn. Celery gebruikt andere diensten voor het verzenden en ontvangen van berichten. Deze berichten zijn meestal taken of resultaten van taken. We gaan Redis gebruiken in deze tutorial voor dit doel. Redis is een geweldige keuze, omdat het heel gemakkelijk te installeren en configureren is en het is echt mogelijk dat u het al in uw toepassing voor andere doeleinden gebruikt, zoals caching en pub / sub. 

U kunt Redis installeren door de instructies op de Redis Quick Start-pagina te volgen. Vergeet niet om de te installeren redis Python-bibliotheek, pip install redis, en de bundel die nodig is voor het gebruik van Redis en Celery: pip install selderij [redis].

Start de Redis-server als volgt: $ redis-server

Om te beginnen met het bouwen van dingen met Celery, zullen we eerst een Celery-applicatie moeten maken. Daarna moet Celery weten wat voor soort taken het kan uitvoeren. Om dat te bereiken, moeten we taken bij de Celery-applicatie registreren. We doen dit met behulp van de @ app.task decorateur:

# celery_squirrel.py importeer tijd van utils import check_website van data-import WEBSITE_LIST van selderijimport Celery van selderij.result import ResultSet app = Celery ('celery_squirrel', broker = "redis: // localhost: 6379/0", backend = "redis : // localhost: 6379/0 ") @ app.task def check_website_task (address): return check_website (address) if __name__ ==" __main__ ": start_time = time.time () # Gebruik van 'delay' voert de asynchrone taak uit = ResultSet ([check_website_task.delay (adres) voor adres in WEBSITE_LIST]) # Wacht tot de taken zijn voltooid rs.get () end_time = time.time () print ("CelerySquirrel:", end_time - start_time) # CelerySquirrel: 2.4979639053344727

Raak niet in paniek als er niets gebeurt. Onthoud dat Celery een service is en dat we het moeten uitvoeren. Tot nu toe hebben we alleen de taken in Redis geplaatst maar zijn we niet met Celery begonnen om ze uit te voeren. Om dit te doen, moeten we deze opdracht uitvoeren in de map waarin onze code zich bevindt:

selderijwerknemer -A do_celery --loglevel = debug --concurrency = 4

Herhaal nu het Python-script en kijk wat er gebeurt. Eén ding om op te letten: let op hoe we het redis-adres tweemaal hebben doorgegeven aan onze Redis-applicatie. De makelaar parameter geeft aan waar de taken worden doorgegeven aan Celery, en backend is waar Celery de resultaten neerzet zodat we ze in onze app kunnen gebruiken. Als we geen resultaat opgeven backend, er is geen manier om te weten wanneer de taak is verwerkt en wat het resultaat was.

Houd er ook rekening mee dat de logboeken zich nu in de standaarduitvoer van het Celery-proces bevinden. Controleer ze dus in de juiste terminal.

conclusies

Ik hoop dat dit een interessante reis voor je is geweest en een goede kennismaking met de wereld van parallelle / gelijktijdige programmering in Python. Dit is het einde van de reis en er zijn enkele conclusies die we kunnen trekken:

  • Er zijn verschillende paradigma's die ons helpen om high-performance computing te bereiken in Python.
  • Voor het multi-threaded paradigma hebben we de threading en concurrent.futures bibliotheken.
  • multiprocessing biedt een zeer vergelijkbare interface naar threading maar voor processen in plaats van threads.
  • Bedenk dat processen een echt parallelisme bereiken, maar dat ze duurder zijn om te maken.
  • Bedenk dat een proces meer threads kan bevatten die erin lopen.
  • Vergis je niet parallel voor gelijktijdig gebruik. Onthoud dat alleen de parallelle benadering voordeel haalt uit multi-coreprocessors, terwijl gelijktijdig programmeren intelligent taken plant zodat het wachten op langlopende operaties wordt gedaan terwijl tegelijkertijd de daadwerkelijke berekening wordt gedaan.

Leer Python

Leer Python met onze complete python-handleiding, of je nu net begint of dat je een ervaren coder bent die op zoek is naar nieuwe vaardigheden.