Započnite s async u Pythonu

Asinkrono programiranje, ili kratica asinkrono , značajka je mnogih modernih jezika koja programu omogućuje žongliranje s više operacija bez čekanja ili prekidanja bilo koje od njih. To je pametan način za učinkovito rješavanje zadataka kao što su mrežni ili datotečni I / O, gdje se većina programa troši na čekanje da zadatak završi.

Razmislite o aplikaciji za struganje weba koja otvara 100 mrežnih veza. Možete otvoriti jednu vezu, pričekati rezultate, a zatim otvoriti sljedeću i čekati rezultate itd. Većinu vremena pokretanja programa provodi se čekajući na mrežni odgovor, ne radeći stvarni posao.

Async vam nudi učinkovitiju metodu: otvorite svih 100 veza odjednom, a zatim se prebacujte između svake aktivne veze dok vraćaju rezultate. Ako jedna veza ne vraća rezultate, prebacite se na sljedeću i tako dalje dok sve veze ne vrate svoje podatke.

Sintaksa async sada je standardna značajka u Pythonu, ali dugogodišnji Pythonisti koji su navikli raditi jednu po jednu stvar mogu imati problema oko omatanja glave. U ovom ćemo članku istražiti kako asinkrono programiranje djeluje u Pythonu i kako ga staviti u upotrebu.

Imajte na umu da ako želite koristiti async u Pythonu, najbolje je koristiti Python 3.7 ili Python 3.8 (najnoviju verziju ovog teksta). Koristit ćemo Pythonovu asinkronu sintaksu i pomoćne funkcije kako su definirane u tim verzijama jezika.

Kada koristiti asinkrono programiranje

Općenito, najbolje vrijeme za upotrebu asinkronizacije je kada pokušavate obaviti posao koji ima sljedeće osobine:

  • Posao traje dugo.
  • Kašnjenje uključuje čekanje I / O (diskovnih ili mrežnih) operacija, a ne izračunavanje.
  • Posao uključuje mnogo I / O operacija koje se događaju odjednom ili jednu ili više I / O operacija koje se događaju kada pokušavate obaviti i druge zadatke.

Async vam omogućuje paralelno postavljanje više zadataka i učinkovito ponavljanje kroz njih, bez blokiranja ostatka aplikacije.

Nekoliko primjera zadataka koji dobro rade s async-om:

  • Web struganje, kako je gore opisano.
  • Mrežne usluge (npr. Web poslužitelj ili okvir).
  • Programi koji koordiniraju rezultate iz više izvora kojima treba dugo vremena za vraćanje vrijednosti (na primjer, istodobni upiti baze podataka).

Važno je napomenuti da se asinkrono programiranje razlikuje od multithreadinga ili multiprocesiranja. Sve se radnje asinkronizacije odvijaju u istoj niti, ali se po potrebi međusobno prepuštaju, čineći asinkronizaciju učinkovitijom od navoja niti višestruke obrade za mnoge vrste zadataka. (Više o tome u nastavku.)

Python asyncawaitiasyncio

Python je nedavno dodao dvije ključne riječi asynci awaitza stvaranje asinkrenih operacija. Razmotrite ovu skriptu:

def get_server_status (server_addr) # Potencijalno dugotrajna operacija ... return server_status def server_ops () results = [] results.append (get_server_status ('addr1.server') results.append (get_server_status ('addr2.server') return) rezultatima 

Asinkronizirana verzija iste skripte - ne funkcionalna, tek toliko da nam pruži ideju o tome kako funkcionira sintaksa - može izgledati ovako.

async def get_server_status (server_addr) # Potencijalno dugotrajna operacija ... return server_status async def server_ops () results = [] results.append (await get_server_status ('addr1.server') results.append (await get_server_status ('addr2. poslužitelj ') vratiti rezultate 

Funkcije s prefiksom asyncključne riječi postaju asinkrone funkcije, poznate i kao suprogrami . Programi se ponašaju drugačije od uobičajenih funkcija:

  • Koprogrami mogu koristiti drugu ključnu riječ, awaitkoja koroutini omogućuje da čeka blokiranje rezultata iz druge koroutine. Dok se rezultati ne vrate s awaited koroutine, Python se slobodno prebacuje između ostalih tekućih koroutina.
  • Programi se mogu pozivati samo s drugih asyncfunkcija. Ako pokrenete skriptu server_ops()ili je onakva get_server_status()kakva jest, nećete dobiti njihove rezultate; dobit ćete Python objekt podprogram koji se ne može izravno koristiti.

Dakle, ako ne možemo pozvati asyncfunkcije iz neasinkronih funkcija i ne možemo asyncizravno pokretati funkcije, kako ih koristimo? Odgovor: Korištenjem asyncioknjižnice koja premošćuje asynci ostatak Pythona.

Python asyncawaiti asyncioprimjer

Evo primjera (opet, ne funkcionalnog, ali ilustrativnog) kako se može napisati program za struganje weba pomoću asynci asyncio. Ova skripta uzima popis URL-ova i koristi više primjeraka asyncfunkcije iz vanjske knjižnice ( read_from_site_async()) za njihovo preuzimanje i objedinjavanje rezultata.

import asyncio from 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'] rezultati = asyncio.run (glavni (urls)) ispis (rezultati) 

U gornjem primjeru koristimo dvije uobičajene asynciofunkcije:

  • asyncio.run()koristi se za pokretanje asyncfunkcije iz neasinhronog dijela našeg koda i time pokretanje svih asinhronih aktivnosti programa. (Ovako trčimo main().)
  • asyncio.gather()uzima jednu ili više asinkro ukrašenih funkcija (u ovom slučaju nekoliko primjeraka read_from_site_async()iz naše hipotetske biblioteke za struganje weba), pokreće ih sve i čeka da svi rezultati uđu.

Ideja je ovdje da započnemo operaciju čitanja odjednom za sve web stranice, a zatim prikupimo rezultate čim stignu (dakle asyncio.gather()). Ne čekamo da se završi bilo koja operacija prije nego što prijeđemo na sljedeću.

Komponente Python async aplikacija

Već smo spomenuli kako Python async aplikacije koriste programe kao glavni sastojak, crpeći asynciobiblioteku da ih pokreću. Nekoliko drugih elemenata također je ključno za asinkrone aplikacije u Pythonu:

Petlje događaja

asyncioKnjižnica stvara i upravlja događaja petlje , mehanizmi koji se pokreću coroutines dok ne završi. U Python procesu istodobno bi se trebala izvoditi samo jedna petlja događaja, makar samo kako bi programeru bilo lakše pratiti što u nju ulazi.

Zadaci

Kada predate podprogram petlji događaja na obradu, možete vratiti Taskobjekt koji pruža način kontrole ponašanja podprograma izvan petlje događaja. Ako, na primjer, trebate otkazati izvršeni zadatak, to možete učiniti pozivom metode zadatka .cancel().

Evo malo drugačije verzije skripte za struganje stranice koja prikazuje petlju događaja i zadatke na poslu:

uvoz asyncio s web_scraping_library uvoz read_from_site_async zadaci = [] async def glavni (url_list): za n u url_list: tasks.append (asyncio.create_task (read_from_site_async (n))) print (zadaci) return await asyncio.gather = ['//site1.com','//othersite.com','//newsite.com'] petlja = asyncio.get_event_loop () rezultati = loop.run_until_complete (glavni (urls)) print (rezultati) 

Ova skripta eksplicitnije koristi petlju događaja i zadatke.

  • .get_event_loop()Metoda omogućava nam s objektom koji nam omogućuje kontrolu događaja petlju izravno, podnošenjem Async funkcije na njega programski putem .run_until_complete(). U prethodnoj skripti mogli smo pokrenuti samo jednu funkciju asinkronizacije najviše razine asyncio.run(). Usput, .run_until_complete() radi upravo ono što kaže: Pokreće sve isporučene zadatke dok ne završe, a zatim vraća njihove rezultate u jednoj seriji.
  • .create_task()Metoda ima funkciju za pokretanje, uključujući i njegove parametre, te nas vraća na Taskobjekt da ga pokrenuti. Ovdje predajemo svaki URL zasebno Taskpetlji događaja i pohranjujemo Taskobjekte na popis. Imajte na umu da to možemo učiniti samo unutar petlje događaja - to jest unutar asyncfunkcije.

How much control you need over the event loop and its tasks will depend on how complex the application is that you’re building. If you just want to submit a set of fixed jobs to run concurrently, as with our web scraper, you won’t need a whole lot of control—just enough to launch jobs and gather the results. 

By contrast, if you’re creating a full-blown web framework, you’ll want far more control over the behavior of the coroutines and the event loop. For instance, you may need to shut down the event loop gracefully in the event of an application crash, or run tasks in a threadsafe manner if you’re calling the event loop from another thread.

Async vs. threading vs. multiprocessing

At this point you may be wondering, why use async instead of threads or multiprocessing, both of which have been long available in Python?

First, there is a key difference between async and threads or multiprocessing, even apart from how those things are implemented in Python. Async is about concurrency, while threads and multiprocessing are about parallelism. Concurrency involves dividing time efficiently among multiple tasks at once—e.g., checking your email while waiting for a register at the grocery store. Parallelism involves multiple agents processing multiple tasks side by side—e.g., having five separate registers open at the grocery store.

Most of the time, async is a good substitute for threading as threading is implemented in Python. This is because Python doesn’t use OS threads but its own cooperative threads, where only one thread is ever running at a time in the interpreter. In comparison to cooperative threads, async provides some key advantages:

  • Async functions are far more lightweight than threads. Tens of thousands of asynchronous operations running at once will have far less overhead than tens of thousands of threads.
  • The structure of async code makes it easier to reason about where tasks pick up and leave off. This means data races and thread safety are less of an issue. Because all tasks in the async event loop run in a single thread, it’s easier for Python (and the developer) to serialize how they access objects in memory.
  • Async operations can be cancelled and manipulated more readily than threads. The Task object we get back from asyncio.create_task() provides us with a handy way to do this.

Multiprocessing in Python, on the other hand, is best for jobs that are heavily CPU-bound rather than I/O-bound. Async actually works hand-in-hand with multiprocessing, as you can use asyncio.run_in_executor() to delegate CPU-intensive jobs to a process pool from a central process, without blocking that central process.

Next steps with Python async

The best first thing to do is build a few, simple async apps of your own. Good examples abound now that asynchronous programming in Python has undergone a few versions and had a couple of years to settle down and become more widely used. The official documentation for asyncio is worth reading over to see what it offers, even if you don’t plan to make use of all of its functions.

Također biste mogli istražiti sve veći broj biblioteka i međuopreme koje pokreće asinkrono napajanje, od kojih mnoge nude asinkrone, neblokirajuće verzije konektora baze podataka, mrežnih protokola i slično. aio-libsSpremište ima neke ključne one, kao što je aiohittpknjižnica za pristup Internetu. Također je vrijedno pretraživati ​​Python Package Index za biblioteke s asyncključnom riječi. Uz nešto poput asinkronog programiranja, najbolji način učenja je vidjeti kako su ga drugi iskoristili.