committed by
GitHub
2 changed files with 506 additions and 0 deletions
@ -0,0 +1,505 @@ |
|||||
|
# Конкурентность и async / await |
||||
|
|
||||
|
Здесь приведена подробная информация об использовании синтаксиса `async def` при написании *функций обработки пути*, а также рассмотрены основы асинхронного программирования, конкурентности и параллелизма. |
||||
|
|
||||
|
## Нет времени?<a name="in-a-hurry"></a> |
||||
|
|
||||
|
<abbr title="too long; didn't read (основная мысль)"><strong>TL;DR:</strong></abbr> |
||||
|
|
||||
|
Допустим, вы используете сторонюю библиотеку, которая требует вызова с ключевым словом `await`: |
||||
|
|
||||
|
```Python |
||||
|
results = await some_library() |
||||
|
``` |
||||
|
|
||||
|
В этом случае *функции обработки пути* необходимо объявлять с использованием синтаксиса `async def`: |
||||
|
|
||||
|
```Python hl_lines="2" |
||||
|
@app.get('/') |
||||
|
async def read_results(): |
||||
|
results = await some_library() |
||||
|
return results |
||||
|
``` |
||||
|
|
||||
|
!!! note |
||||
|
`await` можно использовать только внутри функций, объявленных с использованием `async def`. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Если вы обращаетесь к сторонней библиотеке, которая с чем-то взаимодействует |
||||
|
(с базой данных, API, файловой системой и т. д.), и не имеет поддержки синтаксиса `await` |
||||
|
(что относится сейчас к большинству библиотек для работы с базами данных), то |
||||
|
объявляйте *функции обработки пути* обычным образом с помощью `def`, например: |
||||
|
|
||||
|
```Python hl_lines="2" |
||||
|
@app.get('/') |
||||
|
def results(): |
||||
|
results = some_library() |
||||
|
return results |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Если вашему приложению (странным образом) не нужно ни с чем взаимодействовать и, соответственно, |
||||
|
ожидать ответа, используйте `async def`. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Если вы не уверены, используйте обычный синтаксис `def`. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Примечание**: при необходимости можно смешивать `def` и `async def` в *функциях обработки пути* |
||||
|
и использовать в каждом случае наиболее подходящий синтаксис. А FastAPI сделает с этим всё, что нужно. |
||||
|
|
||||
|
В любом из описанных случаев FastAPI работает асинхронно и очень быстро. |
||||
|
|
||||
|
Однако придерживаясь указанных советов, можно получить дополнительную оптимизацию производительности. |
||||
|
|
||||
|
## Технические подробности |
||||
|
|
||||
|
Современные версии Python поддерживают разработку так называемого **"асинхронного кода"** посредством написания **"сопрограмм"** с использованием синтаксиса **`async` и `await`**. |
||||
|
|
||||
|
Ниже разберём эту фразу по частям: |
||||
|
|
||||
|
* **Асинхронный код** |
||||
|
* **`async` и `await`** |
||||
|
* **Сопрограммы** |
||||
|
|
||||
|
## Асинхронный код |
||||
|
|
||||
|
Асинхронный код означает, что в языке 💬 есть возможность сообщить машине / программе 🤖, |
||||
|
что в определённой точке кода ей 🤖 нужно будет ожидать завершения выполнения *чего-то ещё* в другом месте. Допустим это *что-то ещё* называется "медленный файл" 📝. |
||||
|
|
||||
|
И пока мы ждём завершения работы с "медленным файлом" 📝, компьютер может переключиться для выполнения других задач. |
||||
|
|
||||
|
Но при каждой возможности компьютер / программа 🤖 будет возвращаться обратно. Например, если он 🤖 опять окажется в режиме ожидания, или когда закончит всю работу. В этом случае компьютер 🤖 проверяет, не завершена ли какая-нибудь из текущих задач. |
||||
|
|
||||
|
Потом он 🤖 берёт первую выполненную задачу (допустим, наш "медленный файл" 📝) и продолжает работу, производя с ней необходимые действия. |
||||
|
|
||||
|
Вышеупомянутое "что-то ещё", завершения которого приходится ожидать, обычно относится к достаточно "медленным" операциям <abbr title="Ввода-вывода">I/O</abbr> (по сравнению со скоростью работы процессора и оперативной памяти), например: |
||||
|
|
||||
|
* отправка данных от клиента по сети |
||||
|
* получение клиентом данных, отправленных вашей программой по сети |
||||
|
* чтение системой содержимого файла с диска и передача этих данных программе |
||||
|
* запись на диск данных, которые программа передала системе |
||||
|
* обращение к удалённому API |
||||
|
* ожидание завершения операции с базой данных |
||||
|
* получение результатов запроса к базе данных |
||||
|
* и т. д. |
||||
|
|
||||
|
Поскольку в основном время тратится на ожидание выполнения операций <abbr title="Ввода-вывода">I/O</abbr>, |
||||
|
их обычно называют операциями, <abbr title="I/O bound">ограниченными скоростью ввода-вывода</abbr>. |
||||
|
|
||||
|
Код называют "асинхронным", потому что компьютеру / программе не требуется "синхронизироваться" с медленной задачей и, |
||||
|
будучи в простое, ожидать момента её завершения, с тем чтобы забрать результат и продолжить работу. |
||||
|
|
||||
|
Вместо этого в "асинхронной" системе завершённая задача может немного подождать (буквально несколько микросекунд), |
||||
|
пока компьютер / программа занимается другими важными вещами, с тем чтобы потом вернуться, |
||||
|
забрать результаты выполнения и начать их обрабатывать. |
||||
|
|
||||
|
"Синхронное" исполнение (в противовес "асинхронному") также называют <abbr title="sequential">"последовательным"</abbr>, |
||||
|
потому что компьютер / программа последовательно выполняет все требуемые шаги перед тем, как перейти к следующей задаче, |
||||
|
даже если в процессе приходится ждать. |
||||
|
|
||||
|
### Конкурентность и бургеры |
||||
|
|
||||
|
Тот **асинхронный** код, о котором идёт речь выше, иногда называют **"конкурентностью"**. Она отличается от **"параллелизма"**. |
||||
|
|
||||
|
Да, **конкурентность** и **параллелизм** подразумевают, что разные вещи происходят примерно в одно время. |
||||
|
|
||||
|
Но внутреннее устройство **конкурентности** и **параллелизма** довольно разное. |
||||
|
|
||||
|
Чтобы это понять, представьте такую картину: |
||||
|
|
||||
|
### Конкурентные бургеры |
||||
|
|
||||
|
<!-- The gender neutral cook emoji "🧑🍳" does not render well in browsers. In the meantime, I'm using a mix of male "👨🍳" and female "👩🍳" cooks. --> |
||||
|
|
||||
|
Вы идёте со своей возлюбленной 😍 в фастфуд 🍔 и становитесь в очередь, в это время кассир 💁 принимает заказы у посетителей перед вами. |
||||
|
|
||||
|
Когда наконец подходит очередь, вы заказываете парочку самых вкусных и навороченных бургеров 🍔, один для своей возлюбленной 😍, а другой себе. |
||||
|
|
||||
|
Отдаёте деньги 💸. |
||||
|
|
||||
|
Кассир 💁 что-то говорит поварам на кухне 👨🍳, теперь они знают, какие бургеры нужно будет приготовить 🍔 |
||||
|
(но пока они заняты бургерами предыдущих клиентов). |
||||
|
|
||||
|
Кассир 💁 отдаёт вам чек с номером заказа. |
||||
|
|
||||
|
В ожидании еды вы идёте со своей возлюбленной 😍 выбрать столик, садитесь и довольно продолжительное время общаетесь 😍 |
||||
|
(поскольку ваши бургеры самые навороченные, готовятся они не так быстро ✨🍔✨). |
||||
|
|
||||
|
Сидя за столиком с возлюбленной 😍 в ожидании бургеров 🍔, вы отлично проводите время, |
||||
|
восхищаясь её великолепием, красотой и умом ✨😍✨. |
||||
|
|
||||
|
Всё ещё ожидая заказ и болтая со своей возлюбленной 😍, время от времени вы проверяете, |
||||
|
какой номер горит над прилавком, и не подошла ли уже ваша очередь. |
||||
|
|
||||
|
И вот наконец настаёт этот момент, и вы идёте к стойке, чтобы забрать бургеры 🍔 и вернуться за столик. |
||||
|
|
||||
|
Вы со своей возлюбленной 😍 едите бургеры 🍔 и отлично проводите время ✨. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
А теперь представьте, что в этой небольшой истории вы компьютер / программа 🤖. |
||||
|
|
||||
|
В очереди вы просто глазеете по сторонам 😴, ждёте и ничего особо "продуктивного" не делаете. |
||||
|
Но очередь движется довольно быстро, поскольку кассир 💁 только принимает заказы (а не занимается приготовлением еды), так что ничего страшного. |
||||
|
|
||||
|
Когда подходит очередь вы наконец предпринимаете "продуктивные" действия 🤓: просматриваете меню, выбираете в нём что-то, узнаёте, что хочет ваша возлюбленная 😍, собираетесь оплатить 💸, смотрите, какую достали карту, проверяете, чтобы с вас списали верную сумму, и что в заказе всё верно и т. д. |
||||
|
|
||||
|
И хотя вы всё ещё не получили бургеры 🍔, ваша работа с кассиром 💁 ставится "на паузу" ⏸, |
||||
|
поскольку теперь нужно ждать 🕙, когда заказ приготовят. |
||||
|
|
||||
|
Но отойдя с номерком от прилавка, вы садитесь за столик и можете переключить 🔀 внимание |
||||
|
на свою возлюбленную 😍 и "работать" ⏯ 🤓 уже над этим. И вот вы снова очень |
||||
|
"продуктивны" 🤓, мило болтаете вдвоём и всё такое 😍. |
||||
|
|
||||
|
В какой-то момент кассир 💁 поместит на табло ваш номер, подразумевая, что бургеры готовы 🍔, но вы не станете подскакивать как умалишённый, лишь только увидев на экране свою очередь. Вы уверены, что ваши бургеры 🍔 никто не утащит, ведь у вас свой номерок, а у других свой. |
||||
|
|
||||
|
Поэтому вы подождёте, пока возлюбленная 😍 закончит рассказывать историю (закончите текущую работу ⏯ / задачу в обработке 🤓), |
||||
|
и мило улыбнувшись, скажете, что идёте забирать заказ ⏸. |
||||
|
|
||||
|
И вот вы подходите к стойке 🔀, к первоначальной задаче, которая уже завершена ⏯, берёте бургеры 🍔, говорите спасибо и относите заказ за столик. На этом заканчивается этап / задача взаимодействия с кассой ⏹. |
||||
|
В свою очередь порождается задача "поедание бургеров" 🔀 ⏯, но предыдущая ("получение бургеров") завершена ⏹. |
||||
|
|
||||
|
### Параллельные бургеры |
||||
|
|
||||
|
Теперь представим, что вместо бургерной "Конкурентные бургеры" вы решили сходить в "Параллельные бургеры". |
||||
|
|
||||
|
И вот вы идёте со своей возлюбленной 😍 отведать параллельного фастфуда 🍔. |
||||
|
|
||||
|
Вы становитесь в очередь пока несколько (пусть будет 8) кассиров, которые по совместительству ещё и повары 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳, принимают заказы у посетителей перед вами. |
||||
|
|
||||
|
При этом клиенты не отходят от стойки и ждут 🕙 получения еды, поскольку каждый |
||||
|
из 8 кассиров идёт на кухню готовить бургеры 🍔, а только потом принимает следующий заказ. |
||||
|
|
||||
|
Наконец настаёт ваша очередь, и вы просите два самых навороченных бургера 🍔, один для дамы сердца 😍, а другой себе. |
||||
|
|
||||
|
Ни о чём не жалея, расплачиваетесь 💸. |
||||
|
|
||||
|
И кассир уходит на кухню 👨🍳. |
||||
|
|
||||
|
Вам приходится ждать перед стойкой 🕙, чтобы никто по случайности не забрал ваши бургеры 🍔, ведь никаких номерков у вас нет. |
||||
|
|
||||
|
Поскольку вы с возлюбленной 😍 хотите получить заказ вовремя 🕙, и следите за тем, чтобы никто не вклинился в очередь, |
||||
|
у вас не получается уделять должного внимание своей даме сердца 😞. |
||||
|
|
||||
|
Это "синхронная" работа, вы "синхронизированы" с кассиром/поваром 👨🍳. Приходится ждать 🕙 у стойки, |
||||
|
когда кассир/повар 👨🍳 закончит делать бургеры 🍔 и вручит вам заказ, иначе его случайно может забрать кто-то другой. |
||||
|
|
||||
|
Наконец кассир/повар 👨🍳 возвращается с бургерами 🍔 после невыносимо долгого ожидания 🕙 за стойкой. |
||||
|
|
||||
|
Вы скорее забираете заказ 🍔 и идёте с возлюбленной 😍 за столик. |
||||
|
|
||||
|
Там вы просто едите эти бургеры, и на этом всё 🍔 ⏹. |
||||
|
|
||||
|
Вам не особо удалось пообщаться, потому что большую часть времени 🕙 пришлось провести у кассы 😞. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
В описанном сценарии вы компьютер / программа 🤖 с двумя исполнителями (вы и ваша возлюбленная 😍), |
||||
|
на протяжении долгого времени 🕙 вы оба уделяете всё внимание ⏯ задаче "ждать на кассе". |
||||
|
|
||||
|
В этом ресторане быстрого питания 8 исполнителей (кассиров/поваров) 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳. |
||||
|
Хотя в бургерной конкурентного типа было всего два (один кассир и один повар) 💁 👨🍳. |
||||
|
|
||||
|
Несмотря на обилие работников, опыт в итоге получился не из лучших 😞. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Так бы выглядел аналог истории про бургерную 🍔 в "параллельном" мире. |
||||
|
|
||||
|
Вот более реалистичный пример. Представьте себе банк. |
||||
|
|
||||
|
До недавних пор в большинстве банков было несколько кассиров 👨💼👨💼👨💼👨💼 и длинные очереди 🕙🕙🕙🕙🕙🕙🕙🕙. |
||||
|
|
||||
|
Каждый кассир обслуживал одного клиента, потом следующего 👨💼⏯. |
||||
|
|
||||
|
Нужно было долгое время 🕙 стоять перед окошком вместе со всеми, иначе пропустишь свою очередь. |
||||
|
|
||||
|
Сомневаюсь, что у вас бы возникло желание прийти с возлюбленной 😍 в банк 🏦 оплачивать налоги. |
||||
|
|
||||
|
### Выводы о бургерах |
||||
|
|
||||
|
В нашей истории про поход в фастфуд за бургерами приходится много ждать 🕙, |
||||
|
поэтому имеет смысл организовать конкурентную систему ⏸🔀⏯. |
||||
|
|
||||
|
И то же самое с большинством веб-приложений. |
||||
|
|
||||
|
Пользователей очень много, но ваш сервер всё равно вынужден ждать 🕙 запросы по их слабому интернет-соединению. |
||||
|
|
||||
|
Потом снова ждать 🕙, пока вернётся ответ. |
||||
|
|
||||
|
<!--https://forum.wordreference.com/threads/%D0%9D%D0%BE-%D0%B5%D1%81%D0%BB%D0%B8.3258695/--> |
||||
|
Это ожидание 🕙 измеряется микросекундами, но если всё сложить, то набегает довольно много времени. |
||||
|
|
||||
|
Вот почему есть смысл использовать асинхронное ⏸🔀⏯ программирование при построении веб-API. |
||||
|
|
||||
|
Большинство популярных фреймворков (включая Flask и Django) создавались |
||||
|
до появления в Python новых возможностей асинхронного программирования. Поэтому |
||||
|
их можно разворачивать с поддержкой параллельного исполнения или асинхронного |
||||
|
программирования старого типа, которое не настолько эффективно. |
||||
|
|
||||
|
При том, что основная спецификация асинхронного взаимодействия Python с веб-сервером |
||||
|
(<a href="https://asgi.readthedocs.io" class="external-link" target="_blank">ASGI</a>) |
||||
|
была разработана командой Django для внедрения поддержки веб-сокетов. |
||||
|
|
||||
|
Именно асинхронность сделала NodeJS таким популярным (несмотря на то, что он не параллельный), |
||||
|
и в этом преимущество Go как языка программирования. |
||||
|
|
||||
|
И тот же уровень производительности даёт **FastAPI**. |
||||
|
|
||||
|
Поскольку можно использовать преимущества параллелизма и асинхронности вместе, |
||||
|
вы получаете производительность лучше, чем у большинства протестированных NodeJS фреймворков |
||||
|
и на уровне с Go, который является компилируемым языком близким к C <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(всё благодаря Starlette)</a>. |
||||
|
|
||||
|
### Получается, конкурентность лучше параллелизма? |
||||
|
|
||||
|
Нет! Мораль истории совсем не в этом. |
||||
|
|
||||
|
Конкурентность отличается от параллелизма. Она лучше в **конкретных** случаях, где много времени приходится на ожидание. |
||||
|
Вот почему она зачастую лучше параллелизма при разработке веб-приложений. Но это не значит, что конкурентность лучше в любых сценариях. |
||||
|
|
||||
|
Давайте посмотрим с другой стороны, представьте такую картину: |
||||
|
|
||||
|
> Вам нужно убраться в большом грязном доме. |
||||
|
|
||||
|
*Да, это вся история*. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Тут не нужно нигде ждать 🕙, просто есть куча работы в разных частях дома. |
||||
|
|
||||
|
Можно организовать очередь как в примере с бургерами, сначала гостиная, потом кухня, |
||||
|
но это ни на что не повлияет, поскольку вы нигде не ждёте 🕙, а просто трёте да моете. |
||||
|
|
||||
|
И понадобится одинаковое количество времени с очередью (конкурентностью) и без неё, |
||||
|
и работы будет сделано тоже одинаковое количество. |
||||
|
|
||||
|
Однако в случае, если бы вы могли привести 8 бывших кассиров/поваров, а ныне уборщиков 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳, |
||||
|
и каждый из них (вместе с вами) взялся бы за свой участок дома, |
||||
|
с такой помощью вы бы закончили намного быстрее, делая всю работу **параллельно**. |
||||
|
|
||||
|
В описанном сценарии каждый уборщик (включая вас) был бы исполнителем, занятым на своём участке работы. |
||||
|
|
||||
|
И поскольку большую часть времени выполнения занимает реальная работа (а не ожидание), |
||||
|
а работу в компьютере делает <abbr title="Центральный процессор (CPU)">ЦП</abbr>, |
||||
|
такие задачи называют <abbr title="CPU bound">ограниченными производительностью процессора</abbr>. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Ограничение по процессору проявляется в операциях, где требуется выполнять сложные математические вычисления. |
||||
|
|
||||
|
Например: |
||||
|
|
||||
|
* Обработка **звука** или **изображений**. |
||||
|
* **Компьютерное зрение**: изображение состоит из миллионов пикселей, в каждом пикселе 3 составляющих цвета, |
||||
|
обработка обычно требует проведения расчётов по всем пикселям сразу. |
||||
|
* **Машинное обучение**: здесь обычно требуется умножение "матриц" и "векторов". |
||||
|
Представьте гигантскую таблицу с числами в Экселе, и все их надо одновременно перемножить. |
||||
|
* **Глубокое обучение**: это область *машинного обучения*, поэтому сюда подходит то же описание. |
||||
|
Просто у вас будет не одна таблица в Экселе, а множество. В ряде случаев используется |
||||
|
специальный процессор для создания и / или использования построенных таким образом моделей. |
||||
|
|
||||
|
### Конкурентность + параллелизм: Веб + машинное обучение |
||||
|
|
||||
|
**FastAPI** предоставляет возможности конкуретного программирования, |
||||
|
которое очень распространено в веб-разработке (именно этим славится NodeJS). |
||||
|
|
||||
|
Кроме того вы сможете использовать все преимущества параллелизма и |
||||
|
<abbr title="multiprocessing">многопроцессорности</abbr> (когда несколько процессов работают параллельно), |
||||
|
если рабочая нагрузка предполагает **ограничение по процессору**, |
||||
|
как, например, в системах машинного обучения. <!--http://new.gramota.ru/spravka/punctum?layout=item&id=58_329--> |
||||
|
|
||||
|
Необходимо также отметить, что Python является главным языком в области |
||||
|
<abbr title="наука о данных (data science)">**дата-сайенс**</abbr>, |
||||
|
машинного обучения и, особенно, глубокого обучения. Всё это делает FastAPI |
||||
|
отличным вариантом (среди многих других) для разработки веб-API и приложений |
||||
|
в области дата-сайенс / машинного обучения. |
||||
|
|
||||
|
Как добиться такого параллелизма в эксплуатации описано в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}. |
||||
|
|
||||
|
## `async` и `await` |
||||
|
|
||||
|
В современных версиях Python разработка асинхронного кода реализована очень интуитивно. |
||||
|
Он выглядит как обычный "последовательный" код и самостоятельно выполняет "ожидание", когда это необходимо. |
||||
|
|
||||
|
Если некая операция требует ожидания перед тем, как вернуть результат, и |
||||
|
поддерживает современные возможности Python, код можно написать следующим образом: |
||||
|
|
||||
|
```Python |
||||
|
burgers = await get_burgers(2) |
||||
|
``` |
||||
|
|
||||
|
Главное здесь слово `await`. Оно сообщает интерпретатору, что необходимо дождаться ⏸ |
||||
|
пока `get_burgers(2)` закончит свои дела 🕙, и только после этого сохранить результат в `burgers`. |
||||
|
Зная это, Python может пока переключиться на выполнение других задач 🔀 ⏯ |
||||
|
(например получение следующего запроса).<!--http://new.gramota.ru/spravka/buro/search-answer?s=296614--> |
||||
|
|
||||
|
Чтобы ключевое слово `await` сработало, оно должно находиться внутри функции, |
||||
|
которая поддерживает асинхронность. Для этого вам просто нужно объявить её как `async def`: |
||||
|
|
||||
|
```Python hl_lines="1" |
||||
|
async def get_burgers(number: int): |
||||
|
# Готовим бургеры по специальному асинхронному рецепту |
||||
|
return burgers |
||||
|
``` |
||||
|
|
||||
|
...вместо `def`: |
||||
|
|
||||
|
```Python hl_lines="2" |
||||
|
# Это не асинхронный код |
||||
|
def get_sequential_burgers(number: int): |
||||
|
# Готовим бургеры последовательно по шагам |
||||
|
return burgers |
||||
|
``` |
||||
|
|
||||
|
Объявление `async def` указывает интерпретатору, что внутри этой функции |
||||
|
следует ожидать выражений `await`, и что можно поставить выполнение такой функции на "паузу" ⏸ и |
||||
|
переключиться на другие задачи 🔀, с тем чтобы вернуться сюда позже. |
||||
|
|
||||
|
Если вы хотите вызвать функцию с `async def`, вам нужно <abbr title="await">"ожидать"</abbr> её. |
||||
|
Поэтому такое не сработает: |
||||
|
|
||||
|
```Python |
||||
|
# Это не заработает, поскольку get_burgers объявлена с использованием async def |
||||
|
burgers = get_burgers(2) |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Если сторонняя библиотека требует вызывать её с ключевым словом `await`, |
||||
|
необходимо писать *функции обработки пути* с использованием `async def`, например: |
||||
|
|
||||
|
```Python hl_lines="2-3" |
||||
|
@app.get('/burgers') |
||||
|
async def read_burgers(): |
||||
|
burgers = await get_burgers(2) |
||||
|
return burgers |
||||
|
``` |
||||
|
|
||||
|
### Технические подробности |
||||
|
|
||||
|
Как вы могли заметить, `await` может применяться только в функциях, объявленных с использованием `async def`. |
||||
|
|
||||
|
<!--http://new.gramota.ru/spravka/punctum?layout=item&id=58_128--> |
||||
|
Но выполнение такой функции необходимо "ожидать" с помощью `await`. |
||||
|
Это означает, что её можно вызвать только из другой функции, которая тоже объявлена с `async def`. |
||||
|
|
||||
|
Но как же тогда появилась первая <abbr title="или яйцо?🤔">курица</abbr>? В смысле... как нам вызвать первую асинхронную функцию? |
||||
|
|
||||
|
При работе с **FastAPI** просто не думайте об этом, потому что "первой" функцией является ваша *функция обработки пути*, |
||||
|
и дальше с этим разберётся FastAPI. |
||||
|
|
||||
|
Кроме того, если хотите, вы можете использовать синтаксис `async` / `await` и без FastAPI. |
||||
|
|
||||
|
### Пишите свой асинхронный код |
||||
|
|
||||
|
Starlette (и **FastAPI**) основаны на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, что делает их совместимыми как со стандартной библиотекой <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> в Python, так и с <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>.<!--http://new.gramota.ru/spravka/buro/search-answer?s=285295--> |
||||
|
|
||||
|
В частности, вы можете напрямую использовать <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> в тех проектах, где требуется более сложная логика работы с конкурентностью. |
||||
|
|
||||
|
Даже если вы не используете FastAPI, вы можете писать асинхронные приложения с помощью <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, чтобы они были максимально совместимыми и получали его преимущества (например *структурную конкурентность*). |
||||
|
|
||||
|
### Другие виды асинхронного программирования |
||||
|
|
||||
|
Стиль написания кода с `async` и `await` появился в языке Python относительно недавно. |
||||
|
|
||||
|
Но он сильно облегчает работу с асинхронным кодом. |
||||
|
|
||||
|
Ровно такой же синтаксис (ну или почти такой же) недавно был включён в современные версии JavaScript (в браузере и NodeJS). |
||||
|
|
||||
|
До этого поддержка асинхронного кода была реализована намного сложнее, и его было труднее воспринимать. |
||||
|
|
||||
|
В предыдущих версиях Python для этого использовались потоки или <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>. Но такой код намного сложнее понимать, отлаживать и мысленно представлять. |
||||
|
|
||||
|
Что касается JavaScript (в браузере и NodeJS), раньше там использовали для этой цели |
||||
|
<abbr title="callback">"обратные вызовы"</abbr>. Что выливалось в |
||||
|
<a href="http://callbackhell.ru/" class="external-link" target="_blank">ад обратных вызовов</a>. |
||||
|
|
||||
|
## Сопрограммы |
||||
|
|
||||
|
<abbr title="coroutine">**Корути́на**</abbr> (или же сопрограмма) — это крутое словечко для именования той сущности, |
||||
|
которую возвращает функция `async def`. Python знает, что её можно запустить, как и обычную функцию, |
||||
|
но кроме того сопрограмму можно поставить на паузу ⏸ в том месте, где встретится слово `await`. |
||||
|
|
||||
|
Всю функциональность асинхронного программирования с использованием `async` и `await` |
||||
|
часто обобщают словом "корутины". Они аналогичны <abbr title="Goroutines">"горутинам"</abbr>, ключевой особенности |
||||
|
языка Go. |
||||
|
|
||||
|
## Заключение |
||||
|
|
||||
|
В самом начале была такая фраза: |
||||
|
|
||||
|
> Современные версии Python поддерживают разработку так называемого |
||||
|
**"асинхронного кода"** посредством написания **"сопрограмм"** с использованием |
||||
|
синтаксиса **`async` и `await`**. |
||||
|
|
||||
|
Теперь всё должно звучать понятнее. ✨ |
||||
|
|
||||
|
На этом основана работа FastAPI (посредством Starlette), и именно это |
||||
|
обеспечивает его высокую производительность. |
||||
|
|
||||
|
## Очень технические подробности |
||||
|
|
||||
|
!!! warning |
||||
|
Этот раздел читать не обязательно. |
||||
|
|
||||
|
Здесь приводятся подробности внутреннего устройства **FastAPI**. |
||||
|
|
||||
|
Но если вы обладаете техническими знаниями (корутины, потоки, блокировка и т. д.) |
||||
|
и вам интересно, как FastAPI обрабатывает `async def` в отличие от обычных `def`, |
||||
|
читайте дальше. |
||||
|
|
||||
|
### Функции обработки пути |
||||
|
|
||||
|
Когда вы объявляете *функцию обработки пути* обычным образом с ключевым словом `def` |
||||
|
вместо `async def`, FastAPI ожидает её выполнения, запустив функцию во внешнем |
||||
|
<abbr title="threadpool">пуле потоков</abbr>, а не напрямую (это бы заблокировало сервер). |
||||
|
|
||||
|
Если ранее вы использовали другой асинхронный фреймворк, который работает иначе, |
||||
|
и привыкли объявлять простые вычислительные *функции* через `def` ради |
||||
|
незначительного прироста скорости (порядка 100 наносекунд), обратите внимание, |
||||
|
что с **FastAPI** вы получите противоположный эффект. В таком случае больше подходит |
||||
|
`async def`, если только *функция обработки пути* не использует код, приводящий |
||||
|
к блокировке <abbr title="Ввод/вывод: чтение и запись на диск, сетевые соединения.">I/O</abbr>. |
||||
|
<!--Уточнить: Не использовать async def, если код приводит к блокировке IO?--> |
||||
|
|
||||
|
<!--http://new.gramota.ru/spravka/punctum?layout=item&id=58_285--> |
||||
|
Но в любом случае велика вероятность, что **FastAPI** [окажется быстрее](/#performance){.internal-link target=_blank} |
||||
|
другого фреймворка (или хотя бы на уровне с ним). |
||||
|
|
||||
|
### Зависимости |
||||
|
|
||||
|
То же относится к зависимостям. Если это обычная функция `def`, а не `async def`, |
||||
|
она запускается во внешнем пуле потоков. |
||||
|
|
||||
|
### Подзависимости |
||||
|
|
||||
|
Вы можете объявить множество ссылающихся друг на друга зависимостей и подзависимостей |
||||
|
(в виде параметров при определении функции). Какие-то будут созданы с помощью `async def`, |
||||
|
другие обычным образом через `def`, и такая схема вполне работоспособна. Функции, |
||||
|
объявленные с помощью `def` будут запускаться на внешнем потоке (из пула), |
||||
|
а не с помощью `await`. |
||||
|
|
||||
|
### Другие служебные функции |
||||
|
|
||||
|
Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять |
||||
|
с использованием `def` или `async def`. FastAPI не будет влиять на то, как вы |
||||
|
их запускаете. |
||||
|
|
||||
|
Этим они отличаются от функций, которые FastAPI вызывает самостоятельно: |
||||
|
*функции обработки пути* и зависимости. |
||||
|
|
||||
|
Если служебная функция объявлена с помощью `def`, она будет вызвана напрямую |
||||
|
(как вы и написали в коде), а не в отдельном потоке. Если же она объявлена с |
||||
|
помощью `async def`, её вызов должен осуществляться с ожиданием через `await`. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
<!--http://new.gramota.ru/spravka/buro/search-answer?s=299749--> |
||||
|
Ещё раз повторим, что все эти технические подробности полезны, только если вы специально их искали. |
||||
|
|
||||
|
В противном случае просто ознакомьтесь с основными принципами в разделе выше: <a href="#in-a-hurry">Нет времени?</a>. |
Loading…
Reference in new issue