# Рівночасність і 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">

/// note | Примітка

Прекрасні ілюстрації від [Ketrina Thompson](https://www.instagram.com/ketrinadrawsalot). 🎨

///

---

Уявіть, що в цій історії ви - комп’ютер/програма 🤖.

Поки ви в черзі, ви просто бездіяльні 😴, чекаєте своєї черги, нічого «продуктивного» не роблячи. Але черга рухається швидко, бо касир лише приймає замовлення (а не готує їх), тож це нормально.

Коли ж ваша черга, ви виконуєте справді «продуктивну» роботу: переглядаєте меню, вирішуєте, що бажаєте, дізнаєтеся вибір вашої симпатії, платите, перевіряєте, що віддаєте правильну купюру чи картку, що з вас правильно списали кошти, що замовлення містить правильні позиції тощо.

Але потім, хоча у вас ще немає бургерів, ваша взаємодія з касиром «на паузі» ⏸, бо вам доводиться чекати 🕙, поки бургери будуть готові.

Втім, відійшовши від прилавка і сівши за столик із номерком, ви можете перемкнути 🔀 увагу на свою симпатію і «попрацювати» ⏯ 🤓 над цим. Тоді ви знову робите щось дуже «продуктивне» - фліртуєте зі своєю симпатією 😍.

Потім касир 💁 каже «Я закінчив робити бургери», виводячи ваш номер на табло прилавка, але ви не підстрибуєте миттєво, щойно номер змінюється на ваш. Ви знаєте, що ніхто не вкраде ваші бургери, адже у вас є номер вашої черги, а в інших - свій.

Тож ви чекаєте, поки ваша симпатія завершить історію (завершить поточну роботу ⏯/задачу 🤓), лагідно усміхаєтеся і кажете, що підете за бургерами ⏸.

Потім ви йдете до прилавка 🔀, до початкової задачі, яку тепер завершено ⏯, забираєте бургери, дякуєте і несете їх до столу. Це завершує той крок/задачу взаємодії з прилавком ⏹. Натомість з’являється нова задача «їсти бургери» 🔀 ⏯, але попередня «отримати бургери» завершена ⏹.

### Паралельні бургери { #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">

Багато розмов чи флірту не було, бо більшість часу пішла на очікування 🕙 перед прилавком. 😞

/// note | Примітка

Прекрасні ілюстрації від [Ketrina Thompson](https://www.instagram.com/ketrinadrawsalot). 🎨

///

---

У цьому сценарії паралельних бургерів ви - комп’ютер/програма 🤖 з двома процесорами (ви і ваша симпатія), які обидва чекають 🕙 і приділяють увагу ⏯ «очікуванню біля прилавка» 🕙 тривалий час.

У закладу фастфуду 8 процесорів (касира/кухаря). У той час як у закладу з рівночасними бургерами могло бути лише 2 (один касир і один кухар).

Та все одно фінальний досвід не найкращий. 😞

---

Це була б паралельна історія про бургери. 🍔

Для більш «реального» прикладу уявіть банк.

До недавнього часу більшість банків мали кілька касирів 👨‍💼👨‍💼👨‍💼👨‍💼 і велику чергу 🕙🕙🕙🕙🕙🕙🕙🕙.

Усі касири робили всю роботу з одним клієнтом за іншим 👨‍💼⏯.

І вам доводилося 🕙 довго стояти в черзі, інакше ви втратите свою чергу.

Ви, напевно, не хотіли б брати свою симпатію 😍 із собою у справи до банку 🏦.

### Висновок про бургери { #burger-conclusion }

У цьому сценарії «фастфуд із вашою симпатією», оскільки є багато очікування 🕙, значно доцільніше мати рівночасну систему ⏸🔀⏯.

Так є у більшості вебзастосунків.

Багато-багато користувачів, але ваш сервер чекає 🕙 на їхнє не надто гарне з’єднання, щоб вони надіслали свої запити.

А потім знову чекає 🕙 на повернення відповідей.

Це «очікування» 🕙 вимірюється у мікросекундах, але все ж, у сумі - це багато очікування в підсумку.

Ось чому дуже логічно використовувати асинхронний ⏸🔀⏯ код для веб API.

Такий тип асинхронності зробив NodeJS популярним (хоча NodeJS не є паралельним), і це сила Go як мови програмування.

І такий самий рівень продуктивності ви отримуєте з **FastAPI**.

А оскільки можна мати паралелізм і асинхронність одночасно, ви отримуєте вищу продуктивність, ніж більшість протестованих фреймворків NodeJS, і на рівні з Go, який є компільованою мовою, ближчою до C [(усе завдяки Starlette)](https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1).

### Чи краща рівночасність за паралелізм? { #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).

## `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**) базуються на [AnyIO](https://anyio.readthedocs.io/en/stable/), що робить їх сумісними як зі стандартною бібліотекою Python [asyncio](https://docs.python.org/3/library/asyncio-task.html), так і з [Trio](https://trio.readthedocs.io/en/stable/).

Зокрема, ви можете безпосередньо використовувати [AnyIO](https://anyio.readthedocs.io/en/stable/) для ваших просунутих сценаріїв рівночасності, що потребують складніших патернів у вашому коді.

І навіть якщо ви не використовували FastAPI, ви могли б писати свої власні async-застосунки з [AnyIO](https://anyio.readthedocs.io/en/stable/), щоб мати високу сумісність і отримати його переваги (наприклад, *структурована рівночасність*).

Я створив іншу бібліотеку поверх AnyIO, як тонкий шар, щоб дещо покращити анотації типів і отримати кращу **автодопомогу** (autocompletion), **вбудовані помилки** (inline errors) тощо. Вона також має дружній вступ і навчальний посібник, щоб допомогти вам **зрозуміти** і написати **власний async-код**: [Asyncer](https://asyncer.tiangolo.com/). Вона буде особливо корисною, якщо вам потрібно **поєднувати async-код зі звичайним** (блокуючим/синхронним) кодом.

### Інші форми асинхронного коду { #other-forms-of-asynchronous-code }

Такий стиль використання `async` і `await` відносно новий у мові.

Але він значно полегшує роботу з асинхронним кодом.

Такий самий (або майже ідентичний) синтаксис нещодавно з’явився в сучасних версіях JavaScript (у Browser і NodeJS).

До цього робота з асинхронним кодом була значно складнішою.

У попередніх версіях Python ви могли використовувати потоки (threads) або [Gevent](https://www.gevent.org/). Але код набагато складніший для розуміння, налагодження і мислення про нього.

У попередніх версіях 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) (або принаймні порівнянним) за ваш попередній фреймворк.

### Залежності { #dependencies }

Те саме стосується і [залежностей](tutorial/dependencies/index.md). Якщо залежність є стандартною функцією `def` замість `async def`, вона виконується у зовнішньому пулі потоків.

### Підзалежності { #sub-dependencies }

Ви можете мати кілька залежностей і [підзалежностей](tutorial/dependencies/sub-dependencies.md), які вимагають одна одну (як параметри визначень функцій). Деякі з них можуть бути створені з `async def`, а деякі - зі звичайним `def`. Все працюватиме, і ті, що створені зі звичайним `def`, будуть викликані у зовнішньому потоці (з пулу потоків), а не «очікувані».

### Інші допоміжні функції { #other-utility-functions }

Будь-яка інша допоміжна функція, яку ви викликаєте безпосередньо, може бути створена зі звичайним `def` або `async def`, і FastAPI не впливатиме на спосіб її виклику.

Це відрізняється від функцій, які FastAPI викликає за вас: функції операції шляху і залежності.

Якщо ваша допоміжна функція є звичайною функцією з `def`, її буде викликано безпосередньо (як ви написали у своєму коді), не в пулі потоків; якщо функція створена з `async def`, тоді вам слід використовувати `await` при її виклику у вашому коді.

---

Знову ж таки, це дуже технічні деталі, які, ймовірно, стануть у пригоді, якщо ви спеціально їх шукали.

Інакше вам вистачить настанов із розділу вище: <a href="#in-a-hurry">Поспішаєте?</a>.
