Асинхронне програмування дозволяє розробникам писати код, що здатен виконувати декілька завдань одночасно. В цьому записі буде розглянуто основи асинхронного програмування у Python та використання модулю asyncio.

Вступ

Суть асинхронного програмування полягає у одночасному виконанні завдань, без очікування завершення одного завдання, перш ніж переходити до наступного. На відміну від синхронного програмування, де завдання виконуються одне після одного, послідовно.

У Python асинхронне програмування забезпечується бібліотекою asyncio (скорочення від Asynchronous I/O, асинхронний ввід/вивід), яка надає інструменти для написання та виконання асинхронного коду, використовуючи ключові слова Python async та await.

Переваги асинхронного програмування

Цей підхід особливо корисний у сценаріях, котрі включають операції вводу/виводу (такі як мережеві запити, файловий ввід/вивід, запити до бази даних), що можуть виконуватись тривалий час і блокувати виконання інших завдань.

Перевагою асинхронного програмування, є його здатності ефективно обробляти кілька завдань. Традиційний синхронний код може призвести до виникнення вузьких місць, особливо при обробці завдань, що пов’язані з вводом/виводом. Наприклад, веб-сервер який обробляє декілька клієнтських запитів поспіль, може мати проблеми з синхронним кодом, оскільки йому потрібно дочекатись на завершення одного запиту, перш ніж обробляти наступний. Це може призвести до уповільнення часу відгуку.

Асинхронне програмування вирішує цю проблему, даючи змогу програмі виконувати інші завдання, під час очікування завершення операцій вводу/виводу. Це призводить до зменшення затримок і можливості виконувати більше завдань одночасно.

Основні поняття асинхронного програмування Python

Coroutines (підпрограми)

Coroutines – це спеціальні функції, визначені з використанням виразу async def, виконання котрих можна призупинити та відновити, за допомогою ключового слово await. Коли coroutine очікує на виконання, вона повертає управління програмою до event loop (циклу обробки подій), дозволяючи тим часом програмі виконувати інші завдання.

Event loop

Цикл обробки подій – це центральний механізм, що керує плануванням та виконанням coroutines, та перемикається між coroutines коли вони отримують/передають управління.

Event loop дає змогу Python запускати декілька coroutines одночасно в одному потоці, ефективно використовуючи системні ресурси.

Tasks та Futures

Task – це coroutine, яку заплановано на виконання. В asyncio tasks (задачі), використовуються для конкурентного запуску coroutines.

Future – це об’єкт, який представляє результат асинхронної операції. Його можна використовувати для відстеження ходу виконання task, та отримання результату після її завершення.

Неблокуючий I/O (input/output)

Асинхронне програмування добре підходить для сценаріїв, що включають операції з прив’язкою до I/O. Використовуючи неблокуючий ввід/вивід, програма може продовжувати виконувати інші завдання, під час очікування на завершення операції вводу/виводу.

Concurrency та Parallelism

Concurrency (конкурентність) – передбачає одночасне виконання декількох завдань, тоді як Parallelism (паралелізм) – передбачає одночасне виконання завдань на декількох ядрах процесора. Асинхронне програмування працює з Concurrency, дозволяючи однопоточній програмі ефективно обробляти декілька завдань.

Приклад

Уявімо, що у нас є функція, котра повертає дані після 2-секундної паузи (через наприклад затримку мережевого запиту). Синхронний підхід:

import time


def get_data():
    time.sleep(2)
    return {'data': 'value'}


data = get_data()
print(data) 
# {'data': 'value'}

Функція виконує свою роботу, при цьому все зупиняється в очікуванні встановлених 2 секунд.

Тепер реалізація на asyncio, використовуючи асинхронний спосіб:

import asyncio


async def get_data():
    await asyncio.sleep(2)
    return {'data': 'value'}

Як зазначалось вище, за допомогою виразу async def визначається coroutine. При цьому, простий виклик coroutine (наприклад get_data()), не ініціює її виконання, натомість повертаючи об’єкт coroutine:

data = get_data()
print(data)
# <coroutine object get_data at 0x000002171D3FC6D0>

Для запуску coroutine, потрібно створити event loop (цикл обробки подій), і виконати coroutine всередині нього. Найпростіший спосіб зробити це – використати функцію asyncio.run():

data = asyncio.run(get_data())
print(data)  # {'data': 'value'} 

За допомогою asyncio, поки ми чекаємо, цикл обробки подій може виконувати інші завдання, що робить код неблокуючим і більш ефективним.

Розглянемо ще один приклад:

import asyncio
import time


async def task_one():
    await asyncio.sleep(3)
    print('Task one complete')


async def task_two():
    await asyncio.sleep(2)
    print('Task two complete')


async def main():
    await asyncio.gather(
        task_one(),
        task_two(),
    )

time_start = time.time()

asyncio.run(main())

print('Time execution:', time.time() - time_start)

У цьому прикладі, coroutine функція main() – використовує asyncio.gather(), для одночасного запуску task_one() і task_two(). Це означає, що поки програма чекає на завершення 3-секундної паузи функції task_one(), вона запускає і завершує функцію task_two() (через менший час її сплячого режиму), фактично виконуючи іншу задачу під час очікування. Про це свідчить і сумарний час виконання, 3 секунди замість 5, як мало б бути при синхронному підході в цьому кейсі. Результат виконання програми з прикладу вище:

Task two complete
Task one complete
Time execution: 3.0180201530456543

Підсумок

Асинхронне програмування у Python, пропонує ефективний спосіб для обробки завдань, які передбачають очікування зовнішніх подій, таких як операції input/output. Використовуючи бібліотеку asyncio та її основні концепції – coroutines, event loop, tasks та futures – розробники можуть писати код, котрий буде швидше реагувати на запити.


Мітки: