mirror of https://github.com/tiangolo/fastapi.git
445 lines
38 KiB
Markdown
445 lines
38 KiB
Markdown
# Конкурентность и async / await { #concurrency-and-async-await }
|
||
|
||
Подробности о синтаксисе `async def` для *функций-обработчиков пути* и немного фона об асинхронном коде, конкурентности и параллелизме.
|
||
|
||
## Нет времени? { #in-a-hurry }
|
||
|
||
<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`, даже если внутри не нужен `await`.
|
||
|
||
---
|
||
|
||
Если вы просто не уверены, используйте обычный `def`.
|
||
|
||
---
|
||
|
||
**Примечание**: вы можете смешивать `def` и `async def` в *функциях-обработчиках пути* столько, сколько нужно, и объявлять каждую так, как лучше для вашего случая. FastAPI сделает с ними всё как надо.
|
||
|
||
В любом из случаев выше FastAPI всё равно работает асинхронно и очень быстро.
|
||
|
||
Но следуя этим шагам, он сможет выполнить некоторые оптимизации производительности.
|
||
|
||
## Технические подробности { #technical-details }
|
||
|
||
Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**.
|
||
|
||
Разберём эту фразу по частям в разделах ниже:
|
||
|
||
* **Асинхронный код**
|
||
* **`async` и `await`**
|
||
* **Сопрограммы**
|
||
|
||
## Асинхронный код { #asynchronous-code }
|
||
|
||
Асинхронный код значит, что в языке 💬 есть способ сказать компьютеру/программе 🤖, что в некоторый момент кода ему 🤖 придётся подождать, пока *что-то ещё* где-то в другом месте завершится. Назовём это *что-то ещё* «медленный файл» 📝.
|
||
|
||
И пока мы ждём завершения работы с «медленныи файлом» 📝, компьютер может заняться другой работой.
|
||
|
||
Затем компьютер/программа 🤖 будет возвращаться каждый раз, когда появится возможность (пока снова где-то идёт ожидание), или когда 🤖 завершит всю текущую работу. И он 🤖 проверит, не завершилась ли какая-либо из задач, которых он ждал, и сделает то, что нужно.
|
||
|
||
Далее он 🤖 возьмёт первую завершившуюся задачу (скажем, наш «медленный файл» 📝) и продолжит делать с ней то, что требуется.
|
||
|
||
Это «ожидание чего-то ещё» обычно относится к операциям <abbr title="Input and Output – Ввод/вывод">I/O</abbr>, которые относительно «медленные» (по сравнению со скоростью процессора и оперативной памяти), например ожидание:
|
||
|
||
* отправки данных клиентом по сети
|
||
* получения клиентом данных, отправленных вашей программой по сети
|
||
* чтения системой содержимого файла на диске и передачи этих данных вашей программе
|
||
* записи на диск содержимого, которое ваша программа передала системе
|
||
* операции удалённого API
|
||
* завершения операции базы данных
|
||
* возврата результатов запроса к базе данных
|
||
* и т.д.
|
||
|
||
Поскольку основное время выполнения уходит на ожидание операций <abbr title="Input and Output – Ввод/вывод">I/O</abbr>, их называют операциями, «ограниченными вводом-выводом» (I/O bound).
|
||
|
||
Это называется «асинхронным», потому что компьютеру/программе не нужно «синхронизироваться» с медленной задачей, простаивая и выжидая точный момент её завершения, чтобы забрать результат и продолжить работу.
|
||
|
||
Вместо этого, в «асинхронной» системе, уже завершившаяся задача может немного подождать (несколько микросекунд) в очереди, пока компьютер/программа завершит то, чем занимался, и затем вернётся, чтобы забрать результаты и продолжить работу с ними.
|
||
|
||
Для «синхронного» (в противоположность «асинхронному») исполнения часто используют термин «последовательный», потому что компьютер/программа выполняет все шаги по порядку, прежде чем переключиться на другую задачу, даже если эти шаги включают ожидание.
|
||
|
||
### Конкурентность и бургеры { #concurrency-and-burgers }
|
||
|
||
Та идея **асинхронного** кода, описанная выше, иногда также называется **«конкурентностью»**. Она отличается от **«параллелизма»**.
|
||
|
||
И **конкурентность**, и **параллелизм** относятся к «разным вещам, происходящим примерно одновременно».
|
||
|
||
Но различия между *конкурентностью* и *параллелизмом* довольно существенные.
|
||
|
||
Чтобы их увидеть, представьте следующую историю про бургеры:
|
||
|
||
### Конкурентные бургеры { #concurrent-burgers }
|
||
|
||
Вы идёте со своей возлюбленной за фастфудом, вы стоите в очереди, пока кассир принимает заказы у людей перед вами. 😍
|
||
|
||
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration">
|
||
|
||
Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя. 🍔🍔
|
||
|
||
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration">
|
||
|
||
Кассир говорит что-то повару на кухне, чтобы они знали, что нужно приготовить ваши бургеры (хотя сейчас они готовят бургеры для предыдущих клиентов).
|
||
|
||
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration">
|
||
|
||
Вы платите. 💸
|
||
|
||
Кассир выдаёт вам номер вашей очереди.
|
||
|
||
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration">
|
||
|
||
Пока вы ждёте, вы вместе со своей возлюбленной идёте и выбираете столик, садитесь и долго болтаете (ваши бургеры очень «навороченные», поэтому им нужно время на приготовление).
|
||
|
||
Сидя за столиком со своей возлюбленной в ожидании бургеров, вы можете провести это время, восхищаясь тем, какая она классная, милая и умная ✨😍✨.
|
||
|
||
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration">
|
||
|
||
Пока вы ждёте и разговариваете, время от времени вы поглядываете на номер на табло, чтобы понять, не подошла ли уже ваша очередь.
|
||
|
||
И вот в какой-то момент ваша очередь наступает. Вы подходите к стойке, забираете свои бургеры и возвращаетесь к столику.
|
||
|
||
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration">
|
||
|
||
Вы со своей возлюбленной едите бургеры и отлично проводите время. ✨
|
||
|
||
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration">
|
||
|
||
/// info | Информация
|
||
|
||
Прекрасные иллюстрации от <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||
|
||
///
|
||
|
||
---
|
||
|
||
Представьте, что в этой истории вы — компьютер/программа 🤖.
|
||
|
||
Пока вы стоите в очереди, вы просто бездействуете 😴, ждёте своей очереди и не делаете ничего особо «продуктивного». Но очередь движется быстро, потому что кассир только принимает заказы (а не готовит их), так что это нормально.
|
||
|
||
Когда приходит ваша очередь, вы выполняете действительно «продуктивную» работу: просматриваете меню, решаете, чего хотите, учитываете выбор своей возлюбленной, платите, проверяете, что дали правильную купюру/карту, что сумма списана корректно, что в заказе верные позиции и т.д.
|
||
|
||
Но затем, хотя у вас ещё нет бургеров, ваша «работа» с кассиром поставлена «на паузу» ⏸, потому что нужно подождать 🕙, пока бургеры будут готовы.
|
||
|
||
Но, отойдя от стойки и сев за столик с номерком, вы можете переключить 🔀 внимание на свою возлюбленную и «поработать» ⏯ 🤓 над этим. Снова очень «продуктивно» — флирт с вашей возлюбленной 😍.
|
||
|
||
Потом кассир 💁 «говорит»: «Я закончил делать бургеры», — выводя ваш номер на табло, но вы не подпрыгиваете как сумасшедший в ту же секунду, как только номер сменился на ваш. Вы знаете, что ваши бургеры никто не украдёт, потому что у вас есть номер вашей очереди, а у других — их.
|
||
|
||
Поэтому вы дожидаетесь, пока ваша возлюбленная закончит историю (завершится текущая работа ⏯ / выполняемая задача 🤓), мягко улыбаетесь и говорите, что идёте за бургерами ⏸.
|
||
|
||
Затем вы идёте к стойке 🔀, к исходной задаче, которая теперь завершена ⏯, забираете бургеры, благодарите и несёте их к столику. На этом шаг/задача взаимодействия со стойкой завершён ⏹. Это, в свою очередь, создаёт новую задачу — «есть бургеры» 🔀 ⏯, но предыдущая «получить бургеры» — завершена ⏹.
|
||
|
||
### Параллельные бургеры { #parallel-burgers }
|
||
|
||
Теперь представим, что это не «Конкурентные бургеры», а «Параллельные бургеры».
|
||
|
||
Вы идёте со своей возлюбленной за параллельным фастфудом.
|
||
|
||
Вы стоите в очереди, пока несколько (скажем, 8) кассиров, которые одновременно являются поварами, принимают заказы у людей перед вами.
|
||
|
||
Все перед вами ждут, пока их бургеры будут готовы, не отходя от стойки, потому что каждый из 8 кассиров сразу идёт готовить бургер перед тем, как принять следующий заказ.
|
||
|
||
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration">
|
||
|
||
Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя.
|
||
|
||
Вы платите 💸.
|
||
|
||
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration">
|
||
|
||
Кассир уходит на кухню.
|
||
|
||
Вы ждёте, стоя у стойки 🕙, чтобы никто не забрал ваши бургеры раньше вас, так как никаких номерков нет.
|
||
|
||
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration">
|
||
|
||
Так как вы со своей возлюбленной заняты тем, чтобы никто не встал перед вами и не забрал ваши бургеры, как только они появятся, вы не можете уделить внимание своей возлюбленной. 😞
|
||
|
||
Это «синхронная» работа, вы «синхронизированы» с кассиром/поваром 👨🍳. Вам нужно ждать 🕙 и находиться там в точный момент, когда кассир/повар 👨🍳 закончит бургеры и вручит их вам, иначе их может забрать кто-то другой.
|
||
|
||
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration">
|
||
|
||
Затем ваш кассир/повар 👨🍳 наконец возвращается с вашими бургерами, после долгого ожидания 🕙 у стойки.
|
||
|
||
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration">
|
||
|
||
Вы берёте бургеры и идёте со своей возлюбленной к столику.
|
||
|
||
Вы просто их съедаете — и всё. ⏹
|
||
|
||
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration">
|
||
|
||
Разговоров и флирта было немного, потому что большую часть времени вы ждали 🕙 у стойки. 😞
|
||
|
||
/// info | Информация
|
||
|
||
Прекрасные иллюстрации от <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||
|
||
///
|
||
|
||
---
|
||
|
||
В этом сценарии «параллельных бургеров» вы — компьютер/программа 🤖 с двумя процессорами (вы и ваша возлюбленная), оба ждут 🕙 и уделяют внимание ⏯ тому, чтобы «ждать у стойки» 🕙 долгое время.
|
||
|
||
В ресторане 8 процессоров (кассиров/поваров). Тогда как в «конкурентных бургерах» могло быть только 2 (один кассир и один повар).
|
||
|
||
И всё же финальный опыт — не самый лучший. 😞
|
||
|
||
---
|
||
|
||
Это была параллельная версия истории про бургеры. 🍔
|
||
|
||
Для более «жизненного» примера представьте банк.
|
||
|
||
До недавнего времени в большинстве банков было несколько кассиров 👨💼👨💼👨💼👨💼 и длинная очередь 🕙🕙🕙🕙🕙🕙🕙🕙.
|
||
|
||
Все кассиры делают всю работу с одним клиентом за другим 👨💼⏯.
|
||
|
||
И вам приходится долго 🕙 стоять в очереди, иначе вы потеряете свою очередь.
|
||
|
||
Вы вряд ли захотите взять свою возлюбленную 😍 с собой, чтобы заняться делами в банке 🏦.
|
||
|
||
### Вывод про бургеры { #burger-conclusion }
|
||
|
||
В этом сценарии «фастфуда с вашей возлюбленной», так как много ожидания 🕙, гораздо логичнее иметь конкурентную систему ⏸🔀⏯.
|
||
|
||
Так обстоит дело и с большинством веб-приложений.
|
||
|
||
Очень много пользователей, но ваш сервер ждёт 🕙, пока их не самое хорошее соединение отправит их запросы.
|
||
|
||
А затем снова ждёт 🕙, пока отправятся ответы.
|
||
|
||
Это «ожидание» 🕙 измеряется микросекундами, но если всё сложить, то в сумме получается много ожидания.
|
||
|
||
Вот почему асинхронный ⏸🔀⏯ код очень уместен для веб-API.
|
||
|
||
Именно такая асинхронность сделала NodeJS популярным (хотя 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>.
|
||
|
||
### Конкурентность лучше параллелизма? { #is-concurrency-better-than-parallelism }
|
||
|
||
Нет! Мораль истории не в этом.
|
||
|
||
Конкурентность отличается от параллелизма. И она лучше в **конкретных** сценариях, где много ожидания. Поэтому при разработке веб-приложений она обычно намного лучше параллелизма. Но не во всём.
|
||
|
||
Чтобы уравновесить это, представьте такую короткую историю:
|
||
|
||
> Вам нужно убрать большой грязный дом.
|
||
|
||
*Да, это вся история*.
|
||
|
||
---
|
||
|
||
Здесь нигде нет ожидания 🕙, просто очень много работы в разных местах дома.
|
||
|
||
Можно организовать «очереди» как в примере с бургерами — сначала гостиная, потом кухня, — но так как вы ничего не ждёте 🕙, а просто убираете и убираете, очереди ни на что не повлияют.
|
||
|
||
На завершение уйдёт одинаковое время — с очередями (конкурентностью) и без них — и объём выполненной работы будет одинаковым.
|
||
|
||
Но в этом случае, если бы вы могли привести 8 бывших кассиров/поваров, а теперь — уборщиков, и каждый из них (плюс вы) взял бы свою зону дома для уборки, вы могли бы сделать всю работу **параллельно**, с дополнительной помощью, и завершить гораздо быстрее.
|
||
|
||
В этом сценарии каждый уборщик (включая вас) был бы процессором, выполняющим свою часть работы.
|
||
|
||
И так как основное время выполнения уходит на реальную работу (а не ожидание), а работу в компьютере выполняет <abbr title="Central Processing Unit – Центральный процессор">CPU</abbr>, такие задачи называют «ограниченными процессором» (CPU bound).
|
||
|
||
---
|
||
|
||
Типичные примеры CPU-bound операций — те, которые требуют сложной математической обработки.
|
||
|
||
Например:
|
||
|
||
* Обработка **аудио** или **изображений**.
|
||
* **Компьютерное зрение**: изображение состоит из миллионов пикселей, каждый пиксель имеет 3 значения/цвета; обычно требуется вычислить что-то для всех этих пикселей одновременно.
|
||
* **Машинное обучение**: обычно требует множества умножений «матриц» и «векторов». Представьте огромную таблицу с числами и умножение всех этих чисел «одновременно».
|
||
* **Глубокое обучение**: это подполе Машинного обучения, так что всё вышесказанное применимо. Просто это не одна таблица чисел, а их огромный набор, и во многих случаях вы используете специальный процессор, чтобы строить и/или использовать такие модели.
|
||
|
||
### Конкурентность + параллелизм: Веб + Машинное обучение { #concurrency-parallelism-web-machine-learning }
|
||
|
||
С **FastAPI** вы можете использовать преимущества конкурентности, что очень распространено в веб-разработке (это та же основная «фишка» NodeJS).
|
||
|
||
Но вы также можете использовать выгоды параллелизма и многопроцессности (когда несколько процессов работают параллельно) для рабочих нагрузок, **ограниченных процессором** (CPU bound), как в системах Машинного обучения.
|
||
|
||
Плюс к этому простой факт, что Python — основной язык для **Data Science**, Машинного обучения и особенно Глубокого обучения, делает FastAPI очень хорошим выбором для веб-API и приложений в области Data Science / Машинного обучения (среди многих других).
|
||
|
||
Как добиться такого параллелизма в продакшн, см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}.
|
||
|
||
## `async` и `await` { #async-and-await }
|
||
|
||
В современных версиях Python есть очень интуитивный способ определять асинхронный код. Это делает его похожим на обычный «последовательный» код, а «ожидание» выполняется за вас в нужные моменты.
|
||
|
||
Когда есть операция, которой нужно подождать перед тем, как вернуть результат, и она поддерживает эти новые возможности Python, вы можете написать так:
|
||
|
||
```Python
|
||
burgers = await get_burgers(2)
|
||
```
|
||
|
||
Ключ здесь — `await`. Он говорит Python, что нужно подождать ⏸, пока `get_burgers(2)` закончит своё дело 🕙, прежде чем сохранять результат в `burgers`. Благодаря этому Python будет знать, что за это время можно заняться чем-то ещё 🔀 ⏯ (например, принять другой запрос).
|
||
|
||
Чтобы `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` Python знает, что внутри этой функции нужно учитывать выражения `await` и что выполнение такой функции можно «приостанавливать» ⏸ и идти делать что-то ещё 🔀, чтобы потом вернуться.
|
||
|
||
Когда вы хотите вызвать функцию, объявленную с `async def`, нужно её «ожидать». Поэтому вот так не сработает:
|
||
|
||
```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
|
||
```
|
||
|
||
### Более технические подробности { #more-technical-details }
|
||
|
||
Вы могли заметить, что `await` можно использовать только внутри функций, определённых с `async def`.
|
||
|
||
Но при этом функции, определённые с `async def`, нужно «ожидать». Значит, функции с `async def` тоже можно вызывать только из функций, определённых с `async def`.
|
||
|
||
Так что же с «яйцом и курицей» — как вызвать первую `async` функцию?
|
||
|
||
Если вы работаете с **FastAPI**, вам не о чем беспокоиться, потому что этой «первой» функцией будет ваша *функция-обработчик пути*, а FastAPI знает, как сделать всё правильно.
|
||
|
||
Но если вы хотите использовать `async` / `await` без FastAPI, вы тоже можете это сделать.
|
||
|
||
### Пишите свой асинхронный код { #write-your-own-async-code }
|
||
|
||
Starlette (и **FastAPI**) основаны на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, что делает их совместимыми и со стандартной библиотекой Python <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a>, и с <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>.
|
||
|
||
В частности, вы можете напрямую использовать <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>, чтобы они были максимально совместимыми и получали его преимущества (например, *структурную конкурентность*).
|
||
|
||
Я создал ещё одну библиотеку поверх AnyIO, тонкий слой, чтобы немного улучшить аннотации типов и получить более качественное **автозавершение**, **ошибки прямо в редакторе** и т.д. Там также есть дружелюбное введение и руководство, чтобы помочь вам **понять** и писать **свой собственный асинхронный код**: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. Она особенно полезна, если вам нужно **комбинировать асинхронный код с обычным** (блокирующим/синхронным) кодом.
|
||
|
||
### Другие формы асинхронного кода { #other-forms-of-asynchronous-code }
|
||
|
||
Такой стиль использования `async` и `await` относительно новый в языке.
|
||
|
||
Но он сильно упрощает работу с асинхронным кодом.
|
||
|
||
Такой же (или почти такой же) синтаксис недавно появился в современных версиях JavaScript (в браузере и NodeJS).
|
||
|
||
До этого работа с асинхронным кодом была заметно сложнее и труднее для понимания.
|
||
|
||
В предыдущих версиях Python можно было использовать потоки или <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>. Но такой код гораздо сложнее понимать, отлаживать и держать в голове.
|
||
|
||
В прежних версиях NodeJS/браузерного JavaScript вы бы использовали «callbacks» (обратные вызовы), что приводит к «callback hell» (ад обратных вызовов).
|
||
|
||
## Сопрограммы { #coroutines }
|
||
|
||
**Сопрограмма** (coroutine) — это просто «навороченное» слово для того, что возвращает функция `async def`. Python знает, что это похоже на функцию: её можно запустить, она когда-нибудь завершится, но её выполнение может приостанавливаться ⏸ внутри, когда встречается `await`.
|
||
|
||
Часто всю функциональность использования асинхронного кода с `async` и `await` кратко называют «сопрограммами». Это сопоставимо с ключевой особенностью Go — «goroutines».
|
||
|
||
## Заключение { #conclusion }
|
||
|
||
Вернёмся к той же фразе:
|
||
|
||
> Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**.
|
||
|
||
Теперь это должно звучать понятнее. ✨
|
||
|
||
Именно это «движет» FastAPI (через Starlette) и обеспечивает столь впечатляющую производительность.
|
||
|
||
## Очень технические подробности { #very-technical-details }
|
||
|
||
/// warning | Предупреждение
|
||
|
||
Скорее всего, этот раздел можно пропустить.
|
||
|
||
Здесь — очень технические подробности о том, как **FastAPI** работает «под капотом».
|
||
|
||
Если у вас есть достаточно технических знаний (сопрограммы, потоки, блокировки и т.д.) и вам интересно, как FastAPI обрабатывает `async def` по сравнению с обычным `def`, — вперёд.
|
||
|
||
///
|
||
|
||
### Функции-обработчики пути { #path-operation-functions }
|
||
|
||
Когда вы объявляете *функцию-обработчик пути* обычным `def` вместо `async def`, она запускается во внешнем пуле потоков, который затем «ожидается», вместо прямого вызова (прямой вызов заблокировал бы сервер).
|
||
|
||
Если вы пришли из другого async-фреймворка, который работает иначе, и привыкли объявлять тривиальные *функции-обработчики пути*, выполняющие только вычисления, через простой `def` ради крошечной выгоды в производительности (около 100 наносекунд), обратите внимание: в **FastAPI** эффект будет противоположным. В таких случаях лучше использовать `async def`, если только ваши *функции-обработчики пути* не используют код, выполняющий блокирующий <abbr title="Input/Output – Ввод/вывод: чтение или запись на диск, сетевые соединения.">I/O</abbr>.
|
||
|
||
Тем не менее, в обоих случаях велика вероятность, что **FastAPI** [всё равно будет быстрее](index.md#performance){.internal-link target=_blank} (или как минимум сопоставим) с вашим предыдущим фреймворком.
|
||
|
||
### Зависимости { #dependencies }
|
||
|
||
То же относится к [зависимостям](tutorial/dependencies/index.md){.internal-link target=_blank}. Если зависимость — это обычная функция `def`, а не `async def`, она запускается во внешнем пуле потоков.
|
||
|
||
### Подзависимости { #sub-dependencies }
|
||
|
||
У вас может быть несколько зависимостей и [подзависимостей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, которые требуют друг друга (в виде параметров определений функций): часть из них может быть объявлена с `async def`, а часть — обычным `def`. Всё будет работать, а те, что объявлены обычным `def`, будут вызываться во внешнем потоке (из пула), а не «ожидаться».
|
||
|
||
### Другие служебные функции { #other-utility-functions }
|
||
|
||
Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять обычным `def` или `async def`, и FastAPI не будет влиять на то, как вы их вызываете.
|
||
|
||
В отличие от функций, которые FastAPI вызывает за вас: *функции-обработчики пути* и зависимости.
|
||
|
||
Если служебная функция — обычная функция с `def`, она будет вызвана напрямую (как вы и пишете в коде), не в пуле потоков; если функция объявлена с `async def`, тогда при её вызове в вашем коде вы должны использовать `await`.
|
||
|
||
---
|
||
|
||
Снова: это очень технические подробности, полезные, вероятно, только если вы целенаправленно их ищете.
|
||
|
||
Иначе вам достаточно руководствоваться рекомендациями из раздела выше: <a href="#in-a-hurry">Нет времени?</a>.
|