mirror of https://github.com/tiangolo/fastapi.git
445 lines
37 KiB
Markdown
445 lines
37 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 мають підтримку «асинхронного коду» за допомогою так званих «співпрограм» з синтаксисом **`async` і `await`**.
|
||
|
||
Розгляньмо цю фразу по частинах у секціях нижче:
|
||
|
||
- Асинхронний код
|
||
- `async` і `await`
|
||
- Співпрограми
|
||
|
||
## Асинхронний код { #asynchronous-code }
|
||
|
||
Асинхронний код означає, що мова 💬 має спосіб сказати комп’ютеру/програмі 🤖, що в певний момент у коді він 🤖 має почекати, поки «щось інше» завершиться десь ще. Скажімо, це «щось інше» називається «slow-file» 📝.
|
||
|
||
Отже, в цей час комп’ютер може піти і зробити іншу роботу, доки «slow-file» 📝 завершується.
|
||
|
||
Далі комп’ютер/програма 🤖 повертатиметься щоразу, коли матиме можливість, бо знову чекає, або коли він 🤖 завершив усю роботу, яка була в нього на той момент. І він 🤖 перевірить, чи якась із задач, на які він чекав, уже завершилася, виконавши все, що потрібно.
|
||
|
||
Потім він 🤖 бере першу завершену задачу (скажімо, наш «slow-file» 📝) і продовжує робити те, що потрібно було зробити з нею.
|
||
|
||
Це «чекати на щось інше» зазвичай стосується операцій <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, ви також можете це зробити.
|
||
|
||
### Пишемо свій власний async-код { #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, ви могли б писати свої власні async-застосунки з <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, щоб мати високу сумісність і отримати його переваги (наприклад, *структурована рівночасність*).
|
||
|
||
Я створив іншу бібліотеку поверх AnyIO, як тонкий шар, щоб дещо покращити анотації типів і отримати кращу **автодопомогу** (autocompletion), **вбудовані помилки** (inline errors) тощо. Вона також має дружній вступ і навчальний посібник, щоб допомогти вам **зрозуміти** і написати **власний async-код**: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. Вона буде особливо корисною, якщо вам потрібно **поєднувати async-код зі звичайним** (блокуючим/синхронним) кодом.
|
||
|
||
### Інші форми асинхронного коду { #other-forms-of-asynchronous-code }
|
||
|
||
Такий стиль використання `async` і `await` відносно новий у мові.
|
||
|
||
Але він значно полегшує роботу з асинхронним кодом.
|
||
|
||
Такий самий (або майже ідентичний) синтаксис нещодавно з’явився в сучасних версіях JavaScript (у Browser і NodeJS).
|
||
|
||
До цього робота з асинхронним кодом була значно складнішою.
|
||
|
||
У попередніх версіях Python ви могли використовувати потоки (threads) або <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>. Але код набагато складніший для розуміння, налагодження і мислення про нього.
|
||
|
||
У попередніх версіях NodeJS/Browser JavaScript ви б використовували «callbacks», що призводить до «callback hell».
|
||
|
||
## Співпрограми { #coroutines }
|
||
|
||
**Співпрограма** - це просто дуже вишукана назва для об’єкта, який повертає функція `async def`. Python знає, що це щось на кшталт функції, яку можна запустити і яка завершиться в певний момент, але яку також можна поставити на паузу ⏸ всередині, коли є `await`.
|
||
|
||
Але всю цю функціональність використання асинхронного коду з `async` і `await` часто підсумовують як використання «співпрограм». Це порівняно з головною ключовою особливістю Go - «Goroutines».
|
||
|
||
## Висновок { #conclusion }
|
||
|
||
Погляньмо на ту саму фразу ще раз:
|
||
|
||
> Сучасні версії Python мають підтримку «асинхронного коду» за допомогою так званих «співпрограм», з синтаксисом **`async` і `await`**.
|
||
|
||
Тепер це має більше сенсу. ✨
|
||
|
||
Усе це приводить у дію FastAPI (через Starlette) і дає йому таку вражаючу продуктивність.
|
||
|
||
## Дуже технічні деталі { #very-technical-details }
|
||
|
||
/// warning | Попередження
|
||
|
||
Ймовірно, ви можете пропустити це.
|
||
|
||
Це дуже технічні деталі про те, як **FastAPI** працює «під капотом».
|
||
|
||
Якщо у вас є чимало технічних знань (співпрограми, потоки, блокування тощо) і вам цікаво, як FastAPI обробляє `async def` проти звичайного `def`, - вперед.
|
||
|
||
///
|
||
|
||
### Функції операції шляху { #path-operation-functions }
|
||
|
||
Коли ви оголошуєте функцію операції шляху зі звичайним `def` замість `async def`, вона виконується у зовнішньому пулі потоків (threadpool), який потім «очікується», замість прямого виклику (оскільки прямий виклик блокував би сервер).
|
||
|
||
Якщо ви прийшли з іншого 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>.
|