Кооперативная многозадачность - это вид многозадачности, при котором задачи добровольно передают управление друг другу, а не принудительно прерываются операционной системой. Это позволяет избежать блокировок и гонок данных, если задачи правильно спроектированы.
В asyncio
кооперативная многозадачность реализуется через корутины (coroutines). Корутины - это особый вид функций, которые могут приостанавливать свое выполнение и передавать управление другому коду, а затем возобновлять его с того же места. Ключевые слова async
и await
позволяют определять и использовать корутины.
Пример:
import asyncio
async def task_1():
print("Задача 1: начало")
await asyncio.sleep(1) # Передача управления
print("Задача 1: конец")
async def task_2():
print("Задача 2: начало")
await asyncio.sleep(0.5) # Передача управления
print("Задача 2: конец")
async def main():
await asyncio.gather(task_1(), task_2())
asyncio.run(main())
В этом примере, asyncio.sleep()
приостанавливает выполнение корутины и передает управление event loop'у, который выбирает следующую готовую к выполнению корутину.
Кооперативная многозадачность — это парадигма, при которой несколько задач (или "корутин") выполняются конкурентно, но не параллельно, в рамках одного потока. В отличие от вытесняющей многозадачности, где операционная система или планировщик может в любой момент прервать выполнение задачи и переключиться на другую, в кооперативной многозадачности каждая задача добровольно "отдает" управление, когда достигает точки, где ей нужно подождать, например, завершения ввода-вывода.
Ключевая идея состоит в том, что задачи явно сообщают о готовности отдать управление, что позволяет избежать блокировок и неэффективного использования ресурсов, связанных с принудительным переключением контекста. Это делает кооперативную многозадачность особенно полезной для задач, связанных с большим количеством ввода-вывода, таких как сетевые запросы или чтение из файла.
В Python, библиотека asyncio
предоставляет инструменты для реализации кооперативной многозадачности. Вот как это можно сделать:
Определение корутин: Корутины – это специальные функции, объявленные с помощью ключевого слова async
. Они могут приостанавливать свое выполнение и возобновлять его позже.
import asyncio
async def my_coroutine(name):
print(f"Корутина {name}: начало")
await asyncio.sleep(1) # Симулируем ожидание
print(f"Корутина {name}: завершение")
Использование await
: Ключевое слово await
используется для приостановки выполнения корутины до завершения другой асинхронной операции (например, другой корутины или операции ввода-вывода). Когда задача ожидает, она "отдает" управление циклу событий.
Цикл событий (Event Loop): Цикл событий – это сердце asyncio
. Он управляет планированием и выполнением корутин. Он следит за готовностью разных задач и переключается между ними, когда одна задача отдает управление.
async def main():
task1 = asyncio.create_task(my_coroutine("Первая"))
task2 = asyncio.create_task(my_coroutine("Вторая"))
await asyncio.gather(task1, task2) # Запускаем корутины конкурентно
if __name__ == "__main__":
asyncio.run(main())
Пример:
import asyncio
async def fetch_data(url):
print(f"Начинаем загрузку данных с {url}")
await asyncio.sleep(2) # Симуляция сетевого запроса
print(f"Загрузка данных с {url} завершена")
return f"Данные с {url}"
async def process_data(data):
print(f"Начинаем обработку данных: {data}")
await asyncio.sleep(1) # Симуляция обработки
print(f"Обработка данных {data} завершена")
return f"Обработанные данные: {data}"
async def main():
task1 = asyncio.create_task(fetch_data("https://example.com"))
task2 = asyncio.create_task(fetch_data("https://google.com"))
data1 = await task1
data2 = await task2
processed_data1 = await process_data(data1)
processed_data2 = await process_data(data2)
print(f"Результат 1: {processed_data1}")
print(f"Результат 2: {processed_data2}")
if __name__ == "__main__":
asyncio.run(main())
В этом примере, fetch_data
и process_data
– это корутины. Цикл событий выполняет их конкурентно. Когда fetch_data
ожидает завершения "сетевого запроса" (имитируемого с помощью asyncio.sleep
), он отдает управление циклу событий, который может переключиться на выполнение fetch_data
с другим URL или process_data
, если данные уже готовы.
Преимущества использования asyncio
:
async
и await
для написания асинхронного кода, который выглядит более читаемым и понятным, чем код с обратными вызовами или многопоточностью.Важно помнить, что в кооперативной многозадачности заблокированная корутина блокирует весь цикл событий. Поэтому избегайте длительных вычислений или синхронных операций ввода-вывода внутри корутин. Если необходимо выполнить ресурсоемкую операцию, лучше делегировать её другому потоку или процессу, чтобы не блокировать основной цикл событий.