Programmering

Kom godt i gang med async i Python

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 asynkroniseringvente 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 fra venteed coroutine skifter Python frit mellem andre kørende coroutines.
  • Coroutines kan kun kaldes fra andre asynkronisering funktioner. Hvis du løber server_ops () eller get_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 asynkroniseringvente 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 en asynkronisering fungere fra den ikke-asynkrone del af vores kode og sparke dermed alle progams asynkroniseringsaktiviteter. (Sådan kører vi hoved ().)
  • asyncio.gather () tager en eller flere asynkroniserede funktioner (i dette tilfælde flere forekomster af read_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 af asyncio.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 en Opgave modstand mod at køre det. Her indsender vi hver URL som en separat Opgave til begivenhedssløjfen, og opbevar Opgave objekter på en liste. Bemærk, at vi kun kan gøre dette inde i begivenhedssløjfen - det vil sige inde i en asynkronisering 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 fra asyncio.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.