Започнете с async в Python

Асинхронното програмиране или накратко асинхронно е характеристика на много съвременни езици, която позволява на програмата да жонглира с множество операции, без да чака или да затваря някой от тях. Това е интелигентен начин за ефективно справяне със задачи като мрежови или файлови I / O, където по-голямата част от времето на програмата се прекарва в очакване на задачата да приключи.

Помислете за приложение за уеб изстъргване, което отваря 100 мрежови връзки. Можете да отворите една връзка, да изчакате резултатите, след това да отворите следващата и да изчакате резултатите и т.н. По-голямата част от времето, което програмата изпълнява, прекарва в очакване на мрежов отговор, без да върши действителна работа.

Async ви дава по-ефективен метод: Отворете всичките 100 връзки наведнъж, след което превключете между всяка активна връзка, когато връщат резултати. Ако една връзка не връща резултати, превключете на следващата и така нататък, докато всички връзки върнат данните си.

Асинхронният синтаксис вече е стандартна функция в Python, но дългогодишните Pythonistas, които са свикнали да правят едно и също нещо, може да имат проблеми с обвиването на главите си около него. В тази статия ще изследваме как работи асинхронното програмиране в Python и как да го използваме.

Имайте предвид, че ако искате да използвате асинхронизация в Python, най-добре е да използвате Python 3.7 или Python 3.8 (най-новата версия към момента на писане). Ще използваме асинхронния синтаксис и помощните функции на Python, както е дефинирано в тези версии на езика.

Кога да се използва асинхронно програмиране

Като цяло най-доброто време за използване на асинхронизация е когато се опитвате да вършите работа, която има следните черти:

  • Работата отнема много време.
  • Забавянето включва изчакване на I / O (дискови или мрежови) операции, а не изчисления.
  • Работата включва много I / O операции, които се случват едновременно, или една или повече I / O операции, които се случват, когато се опитвате да свършите и други задачи.

Async ви позволява да настройвате паралелно множество задачи и да ги итератирате ефективно, без да блокирате останалата част от приложението си.

Някои примери за задачи, които работят добре с async:

  • Уеб изстъргване, както е описано по-горе.
  • Мрежови услуги (например уеб сървър или рамка).
  • Програми, които координират резултатите от множество източници, които отнемат много време за връщане на стойности (например едновременни заявки към база данни).

Важно е да се отбележи, че асинхронното програмиране се различава от многонишковото или многопроцесорното. Всички асинхронни операции се изпълняват в една и съща нишка, но при необходимост се поддават една на друга, което прави асинхронизацията по-ефективна от резбоването или многопроцесорната обработка за много видове задачи. (Повече за това по-долу.)

Python asyncawaitиasyncio

Python наскоро добави две ключови думи asyncи awaitза създаване на асинхронни операции. Помислете за този скрипт:

def get_server_status (server_addr) # Потенциално продължителна операция ... връщане server_status def server_ops () results = [] results.append (get_server_status ('addr1.server') results.append (get_server_status ('addr2.server') return) резултати 

Асинхронната версия на същия скрипт - не е функционална, достатъчно, за да ни даде представа как работи синтаксисът - може да изглежда така.

async def get_server_status (server_addr) # Потенциално продължителна операция ... върне server_status async def server_ops () results = [] results.append (await get_server_status ('addr1.server') results.append (await get_server_status ('addr2. сървър ') връща резултати 

Функциите с префикс на asyncключовата дума се превръщат в асинхронни функции, известни също като съпрограми . Програмите се държат по различен начин от обикновените функции:

  • Програмите могат да използват друга ключова дума, awaitкоято позволява на съпрограмата да чака резултати от друга програма без блокиране. Докато резултатите не се върнат от редактираната програма await, Python превключва свободно между останалите работещи програми.
  • Програми могат да се извикват само от други asyncфункции. Ако стартирате server_ops()или get_server_status()както е от тялото на скрипта, няма да получите техните резултати; ще получите съпрограма на Python, която не може да се използва директно.

И така, ако не можем да извикаме asyncфункции от несинхронни функции и не можем да стартираме asyncфункции директно, как да ги използваме? Отговор: Чрез използване на asyncioбиблиотеката, която свързва asyncи останалата част от Python.

Python asyncawaitи asyncioпример

Ето пример (отново, не функционален, но илюстративен) за това как може да се напише приложение за изстъргване в мрежата, използвайки asyncи asyncio. Този скрипт взема списък с URL адреси и използва множество екземпляри на asyncфункция от външна библиотека ( read_from_site_async()), за да ги изтегли и обобщи резултатите.

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'] резултати = asyncio.run (главен (urls)) print (резултати) 

В горния пример използваме две общи asyncioфункции:

  • asyncio.run()се използва за стартиране на asyncфункция от несинхронната част на нашия код и по този начин стартира всички асинхронни дейности на progam. (Ето как тичаме main().)
  • asyncio.gather()взема една или повече асинхронно декорирани функции (в този случай няколко екземпляра read_from_site_async()от нашата хипотетична библиотека за изстъргване на уеб), изпълнява всички тях и изчаква всички резултати да влязат.

Идеята тук е, ние започваме операцията за четене за всички сайтове наведнъж, след което събираме резултатите, когато пристигнат (следователно asyncio.gather()). Не чакаме да завърши някоя операция, преди да преминем към следващата.

Компоненти на асинхронни приложения на Python

Вече споменахме как асинхронните приложения на Python използват съпрограми като основна съставка, като черпят от asyncioбиблиотеката, за да ги стартират. Няколко други елемента също са ключови за асинхронните приложения в Python:

Цикли на събития

В asyncioбиблиотеката създава и управлява вериги за събития , за механизмите, които работят coroutines, докато не се изпълни. Само един цикъл на събития трябва да се изпълнява наведнъж в процес на Python, макар и само за да улесни програмиста да следи какво влиза в него.

Задачи

Когато изпратите съпрограма в цикъл на събития за обработка, можете да получите обратно Taskобект, който предоставя начин за контрол на поведението на съпрограмата извън цикъла на събитията. Ако например трябва да отмените изпълняващата се задача, можете да направите това, като извикате .cancel()метода на задачата .

Here is a slightly different version of the site-scraper script that shows the event loop and tasks at work:

import asyncio from web_scraping_library import read_from_site_async tasks = [] async def main(url_list): for n in url_list: tasks.append(asyncio.create_task(read_from_site_async(n))) print (tasks) return await asyncio.gather(*tasks) urls = ['//site1.com','//othersite.com','//newsite.com'] loop = asyncio.get_event_loop() results = loop.run_until_complete(main(urls)) print (results) 

This script uses the event loop and task objects more explicitly.

  • The .get_event_loop() method provides us with an object that lets us control the event loop directly, by submitting async functions to it programmatically via .run_until_complete(). In the previous script, we could only run a single top-level async function, using asyncio.run(). By the way, .run_until_complete() does exactly what it says: It runs all of the supplied tasks until they’re done, then returns their results in a single batch.
  • The .create_task() method takes a function to run, including its parameters, and gives us back a Task object to run it. Here we submit each URL as a separate Task to the event loop, and store the Task objects in a list. Note that we can only do this inside the event loop—that is, inside an async function.

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.

Можете също така да изследвате нарастващия брой асинхронни библиотеки и междинни програми, много от които предоставят асинхронни, неблокиращи версии на съединители за бази данни, мрежови протоколи и други подобни. В aio-libsхранилището има някои основни такива, като например aiohittpбиблиотека за достъп до мрежата. Също така си струва да търсите в Python Package Index за библиотеки с asyncключовата дума. С нещо като асинхронно програмиране, най-добрият начин да се научите е да видите как другите са го използвали.