Asynkron programmering eller asynkronisering kort sagt, er en funktion af mange moderne sprog, der gør det muligt for et program at jonglere med flere operationer uden at vente eller blive hængt op på en af dem. Det er en smart måde at håndtere opgaver som netværk eller fil I / O effektivt, hvor det meste af programmets tid bruges på at vente på, at en opgave er færdig.
Overvej et webskrabeapplikation, der åbner 100 netværksforbindelser. Du kan åbne en forbindelse, vente på resultaterne, derefter åbne den næste og vente på resultaterne osv. Det meste af tiden, programmet kører, bruges på at vente på et netværkssvar og ikke udføre faktisk arbejde.
Async giver dig en mere effektiv metode: Åbn alle 100 forbindelser på én gang, og skift derefter mellem hver aktiv forbindelse, når de returnerer resultater. Hvis en forbindelse ikke returnerer resultater, skal du skifte til den næste osv., Indtil alle forbindelser har returneret deres data.
Async-syntaks er nu en standardfunktion i Python, men mangeårige Pythonistas, der er vant til at gøre en ting ad gangen, kan have problemer med at pakke hovedet rundt om det. I denne artikel undersøger vi, hvordan asynkron programmering fungerer i Python, og hvordan man bruger den.
Bemærk, at hvis du vil bruge asynkronisering i Python, er det bedst at bruge Python 3.7 eller Python 3.8 (den nyeste version i skrivende stund). Vi bruger Pythons async-syntaks og hjælperfunktioner som defineret i disse versioner af sproget.
Hvornår skal man bruge asynkron programmering
Generelt er de bedste tidspunkter at bruge asynkronisering, når du prøver at udføre arbejde med følgende træk:
- Arbejdet tager lang tid at gennemføre.
- Forsinkelsen indebærer at vente på I / O (disk eller netværk) operationer, ikke beregning.
- Arbejdet involverer mange I / O-operationer på én gang, eller en eller flere I / O-operationer sker, når du også prøver at få andre opgaver udført.
Async giver dig mulighed for at oprette flere opgaver parallelt og gentage dem effektivt uden at blokere resten af din applikation.
Nogle eksempler på opgaver, der fungerer godt med async:
- Webskrabning som beskrevet ovenfor.
- Netværkstjenester (f.eks. En webserver eller framework).
- Programmer, der koordinerer resultater fra flere kilder, der tager lang tid at returnere værdier (for eksempel samtidige databaseforespørgsler).
Det er vigtigt at bemærke, at asynkron programmering er forskellig fra multithreading eller multiprocessing. Async-operationer kører alle i samme tråd, men de giver efter hinanden efter behov, hvilket gør async mere effektiv end threading eller multiprocessing til mange slags opgaver. (Mere om dette nedenfor.)
Python asynkronisering
vente
og asyncio
Python tilføjede for nylig to nøgleord, asynkronisering
og vente
, til oprettelse af asynkroniseringsoperationer. Overvej dette script:
def get_server_status (server_addr) # En potentielt langvarig operation ... return server_status def server_ops () results = [] results.append (get_server_status ('addr1.server') results.append (get_server_status ('addr2.server') return resultater
En asynkroniseret version af det samme script - ikke funktionel, bare nok til at give os en idé om, hvordan syntaksen fungerer - kan se sådan ud.
async def get_server_status (server_addr) # En potentielt langvarig operation ... return server_status async def server_ops () results = [] results.append (await get_server_status ('addr1.server') results.append (await get_server_status ('addr2. server ') returnerer resultater
Funktioner forud for asynkronisering
nøgleord bliver asynkrone funktioner, også kendt som coroutines. Coroutines opfører sig forskelligt fra almindelige funktioner:
- Coroutines kan bruge et andet nøgleord,
vente
, som gør det muligt for en coroutine at vente på resultater fra en anden coroutine uden at blokere. Indtil resultaterne kommer tilbage fravente
ed coroutine skifter Python frit mellem andre kørende coroutines. - Coroutines kan kun kaldes fra andre
asynkronisering
funktioner. Hvis du løberserver_ops ()
ellerget_server_status ()
som det er fra selve scriptet, får du ikke deres resultater; får du et Python coroutine-objekt, som ikke kan bruges direkte.
Så hvis vi ikke kan ringe asynkronisering
funktioner fra ikke-asynkrone funktioner, og vi kan ikke køre asynkronisering
fungerer direkte, hvordan bruger vi dem? Svar: Ved at bruge asyncio
bibliotek, der bygger bro asynkronisering
og resten af Python.
Python asynkronisering
vente
og asyncio
eksempel
Her er et eksempel (igen ikke funktionelt men illustrativt) på, hvordan man kan skrive en webskrabeapplikation ved hjælp af asynkronisering
og asyncio
. Dette script tager en liste over URL'er og bruger flere forekomster af en asynkronisering
funktion fra et eksternt bibliotek (read_from_site_async ()
) for at downloade dem og samle resultaterne.
importer asyncio fra web_scraping_library import read_from_site_async async def main (url_list): return await asyncio.gather (* [read_from_site_async (_) for _ in url_list]) urls = ['//site1.com','//othersite.com', '//newsite.com'] resultater = asyncio.run (main (urls)) print (resultater)
I ovenstående eksempel bruger vi to fælles asyncio
funktioner:
asyncio.run ()
bruges til at starte enasynkronisering
fungere fra den ikke-asynkrone del af vores kode og sparke dermed alle progams asynkroniseringsaktiviteter. (Sådan kører vihoved ()
.)asyncio.gather ()
tager en eller flere asynkroniserede funktioner (i dette tilfælde flere forekomster afread_from_site_async ()
fra vores hypotetiske web-skrabebibliotek), kører dem alle og venter på, at alle resultaterne kommer ind.
Ideen her er, at vi starter læsningsoperationen for alle webstederne på én gang, så samle resultaterne, når de ankommer (dermed asyncio.gather ()
). Vi venter ikke på, at en operation er afsluttet, før vi går videre til den næste.
Komponenter af Python async-apps
Vi har allerede nævnt, hvordan Python async-apps bruger coroutines som deres vigtigste ingrediens, og trækker på asyncio
bibliotek til at køre dem. Et par andre elementer er også nøglen til asynkrone applikationer i Python:
Begivenhedsløjfer
Det asyncio
bibliotek opretter og administrerer begivenhed sløjfer, de mekanismer, der kører coroutines, indtil de er færdige. Kun en begivenhedssløjfe skal køre ad gangen i en Python-proces, hvis kun for at gøre det lettere for programmøren at holde styr på, hvad der går i den.
Opgaver
Når du sender en coroutine til en begivenhedssløjfe til behandling, kan du få tilbage en Opgave
objekt, som giver en måde at kontrollere coroutines opførsel udefra begivenhedssløjfen. Hvis du for eksempel skal annullere den kørende opgave, kan du gøre det ved at ringe til opgavens .afbestille()
metode.
Her er en lidt anden version af site-scraper-scriptet, der viser begivenhedssløjfen og opgaverne på arbejdspladsen:
importer asyncio fra web_scraping_library import read_from_site_async opgaver = [] async def main (url_list): for n i url_list: Tasks.append (asyncio.create_task (read_from_site_async (n))) print (opgaver) returnere afventer asyncio.gather (* = ['//site1.com','//othersite.com','//newsite.com'] loop = asyncio.get_event_loop () results = loop.run_until_complete (main (urls)) print (results)
Dette script bruger begivenhedssløjfen og opgaveobjekter mere eksplicit.
- Det
.get_event_loop ()
metode giver os et objekt, der lader os styre begivenhedssløjfen direkte ved at indsende async-funktioner til den programmatisk via.run_until_complete ()
. I det forrige script kunne vi kun køre en enkelt async-funktion på øverste niveau ved hjælp afasyncio.run ()
. I øvrigt,.run_until_complete ()
gør nøjagtigt, hvad der står: Den kører alle de leverede opgaver, indtil de er færdige, og returnerer derefter deres resultater i en enkelt batch. - Det
.create_task ()
metoden tager en funktion til at køre, inklusive dens parametre, og giver os tilbage enOpgave
modstand mod at køre det. Her indsender vi hver URL som en separatOpgave
til begivenhedssløjfen, og opbevarOpgave
objekter på en liste. Bemærk, at vi kun kan gøre dette inde i begivenhedssløjfen - det vil sige inde i enasynkronisering
fungere.
Hvor meget kontrol du har brug for over begivenhedssløjfen og dens opgaver afhænger af, hvor kompleks applikationen du bygger. Hvis du bare vil indsende et sæt faste job, der skal køre samtidigt, som med vores webskraber, har du ikke brug for en hel masse kontrol - lige nok til at starte job og samle resultaterne.
I modsætning hertil, hvis du opretter en fuldt blæst webramme, vil du have langt mere kontrol over coroutines opførsel og begivenhedssløjfen. For eksempel er du muligvis nødt til at lukke hændelsessløjfen yndefuldt i tilfælde af et programnedbrud eller køre opgaver på en trådsikker måde, hvis du ringer til hændelsessløjfen fra en anden tråd.
Async vs. threading vs. multiprocessing
På dette tidspunkt undrer du dig måske over, hvorfor bruge async i stedet for tråde eller multiprocessing, som begge har længe været tilgængelige i Python?
For det første er der en nøgleforskel mellem asynkronisering og tråde eller multiprocessing, selv bortset fra hvordan disse ting implementeres i Python. Async handler om samtidighed, mens tråde og multiprocessing handler om parallelisme. Samtidighed indebærer en effektiv opdeling af tiden mellem flere opgaver på én gang - f.eks. Kontrol af din e-mail, mens du venter på et register i købmanden. Parallelisme involverer flere agenter, der behandler flere opgaver side om side - f.eks. Med fem separate registre åbne i købmanden.
Det meste af tiden er async en god erstatning for threading, da threading implementeres i Python. Dette skyldes, at Python ikke bruger OS-tråde, men dets egne samarbejdstråde, hvor kun en tråd nogensinde kører ad gangen i tolken. Sammenlignet med kooperative tråde giver async nogle vigtige fordele:
- Async-funktioner er langt mere lette end tråde. Titusinder af asynkrone operationer, der kører på én gang, vil have langt mindre omkostninger end titusinder af tråde.
- Strukturen af async-kode gør det lettere at ræsonnere om, hvor opgaver samles op og slipper. Det betyder, at dataløb og trådsikkerhed er mindre et problem. Da alle opgaver i async-hændelsesløkken kører i en enkelt tråd, er det lettere for Python (og udvikleren) at serieisere, hvordan de får adgang til objekter i hukommelsen.
- Async-operationer kan annulleres og manipuleres lettere end tråde. Det
Opgave
objekt vi kommer tilbage fraasyncio.create_task ()
giver os en praktisk måde at gøre dette på.
Multiprocessing i Python er på den anden side bedst til job, der er stærkt CPU-bundet snarere end I / O-bundet. Async fungerer faktisk hånd i hånd med multiprocessing, som du kan bruge asyncio.run_in_executor ()
at delegere CPU-intensive job til en procespulje fra en central proces uden at blokere den centrale proces.
Næste trin med Python async
Den bedste første ting at gøre er at oprette et par, enkle asynkroniserede apps til dig selv. Gode eksempler findes i overflod nu, at asynkron programmering i Python har gennemgået et par versioner og haft et par år til at slå sig ned og blive mere udbredt. Den officielle dokumentation for asyncio
er det værd at læse for at se, hvad det tilbyder, selvom du ikke planlægger at bruge alle dets funktioner.
Du kan også udforske det voksende antal async-drevne biblioteker og middleware, hvoraf mange leverer asynkrone, ikke-blokerende versioner af databasestik, netværksprotokoller og lignende. Det aio-libs
repository har nogle vigtige, som f.eks aiohittp
bibliotek til internetadgang. Det er også værd at søge i Python Package Index for biblioteker med asynkronisering
nøgleord. Med noget som asynkron programmering er den bedste måde at lære at se, hvordan andre har brugt det til brug.