С чем кооперируется кооперативная многозадачность
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
С чем кооперируется кооперативная многозадачность
Отличный вопрос! Давайте разберемся, как работает кооперативная многозадачность:
1. Что такое кооперативная многозадачность
Это когда сама задача решает когда уступить управление другой задаче. Никакого принудительного переключения из ОС.
# Кооперативная многозадачность в Python — это asyncio
import asyncio
async def task1():
print("Task 1: начало")
await asyncio.sleep(1) # ← КООПЕРИРУЕТ (уступает управление)
print("Task 1: конец")
async def task2():
print("Task 2: начало")
await asyncio.sleep(2) # ← КООПЕРИРУЕТ
print("Task 2: конец")
async def main():
await asyncio.gather(task1(), task2())
if __name__ == "__main__":
asyncio.run(main())
# Вывод:
# Task 1: начало
# Task 2: начало
# Task 1: конец
# Task 2: конец
Главное: Task 1 уступает управление Task 2 на точке await.
2. Кооперирует с I/O операциями
Основная область кооперации — это блокирующие I/O операции:
import asyncio
import aiohttp # Асинхронная HTTP библиотека
async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
print(f"Loaded {url}")
await response.text() # ← БЛОКИРУЮЩАЯ операция
# Но благодаря await, управление переходит другой корутине
return await response.json()
async def main():
# Три запроса, но выполняются параллельно, не последовательно
results = await asyncio.gather(
fetch_url("https://api.github.com/users/github"),
fetch_url("https://api.github.com/users/google"),
fetch_url("https://api.github.com/users/microsoft"),
)
# Вместо 30 секунд (3 * 10 сек), выполнится за ~10 секунд
if __name__ == "__main__":
asyncio.run(main())
С чем кооперирует: с сетевыми операциями (HTTP, TCP, UDP).
3. Кооперирует с файловыми операциями
import asyncio
import aiofiles # Асинхронная файловая библиотека
async def read_file(filename):
async with aiofiles.open(filename, 'r') as f:
content = await f.read() # ← Уступает управление
print(f"Прочитал {filename}")
return content
async def main():
# Читаем 3 файла параллельно
results = await asyncio.gather(
read_file("file1.txt"),
read_file("file2.txt"),
read_file("file3.txt"),
)
if __name__ == "__main__":
asyncio.run(main())
С чем кооперирует: с дисковыми операциями.
4. Кооперирует с таймерами и задержками
import asyncio
async def task_with_delay(name, delay):
print(f"{name}: начало")
await asyncio.sleep(delay) # ← Уступает управление на время ожидания
print(f"{name}: конец (прошло {delay}с)")
async def main():
# Задачи выполняются не по очереди, а параллельно
await asyncio.gather(
task_with_delay("Task 1", 2),
task_with_delay("Task 2", 1),
task_with_delay("Task 3", 3),
)
# Всего 3 секунды (max из всех), а не 6 (sum)
if __name__ == "__main__":
asyncio.run(main())
# Вывод:
# Task 1: начало
# Task 2: начало
# Task 3: начало
# Task 2: конец (прошло 1с)
# Task 1: конец (прошло 2с)
# Task 3: конец (прошло 3с)
5. Кооперирует с базами данных
import asyncio
import asyncpg # Асинхронный драйвер PostgreSQL
async def fetch_user(pool, user_id):
async with pool.acquire() as connection:
user = await connection.fetchrow(
'SELECT * FROM users WHERE id = $1',
user_id
) # ← Уступает при ожидании ответа от БД
return user
async def main():
pool = await asyncpg.create_pool('postgresql://localhost/mydb')
# Запросы выполняются параллельно, не по очереди
users = await asyncio.gather(
fetch_user(pool, 1),
fetch_user(pool, 2),
fetch_user(pool, 3),
)
await pool.close()
if __name__ == "__main__":
asyncio.run(main())
С чем кооперирует: с запросами в БД.
6. Как внутренне работает кооперация
┌─────────────────────────────────────────────────────┐
│ Event Loop (один поток) │
├─────────────────────────────────────────────────────┤
│ │
│ Итерация 1: │
│ ├─ Запустить task1 до первого await │
│ │ (отправить HTTP запрос) │
│ ├─ task1 уступает управление │
│ │ │
│ ├─ Запустить task2 до первого await │
│ │ (прочитать файл) │
│ ├─ task2 уступает управление │
│ │ │
│ └─ Ждать события (HTTP ответ или завершение чтения)
│ │
│ Итерация 2: │
│ ├─ HTTP ответ пришёл → возобновить task1 │
│ ├─ task1 уступает управление │
│ │ │
│ └─ Файл прочитан → возобновить task2 │
│ task2 заканчивается │
│ │
└─────────────────────────────────────────────────────┘
7. Кооперирует с очередями (queues)
import asyncio
async def producer(queue):
for i in range(5):
print(f"Производим {i}")
await queue.put(i) # ← Уступает если очередь полная
await asyncio.sleep(0.1)
async def consumer(queue):
while True:
item = await queue.get() # ← Уступает если очередь пуста
print(f"Потребили {item}")
queue.task_done()
async def main():
queue = asyncio.Queue(maxsize=2)
# Производитель и потребитель работают параллельно
await asyncio.gather(
producer(queue),
consumer(queue),
)
if __name__ == "__main__":
asyncio.run(main())
С чем кооперирует: с синхронизацией между задачами.
8. Кооперирует с subprocess / системными командами
import asyncio
async def run_command(cmd):
process = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate() # ← Уступает при ожидании
return stdout.decode()
async def main():
# Команды выполняются параллельно
results = await asyncio.gather(
run_command("sleep 2 && echo 'Done 1'"),
run_command("sleep 1 && echo 'Done 2'"),
run_command("sleep 3 && echo 'Done 3'"),
)
# Выполнится за ~3 секунды вместо 6
if __name__ == "__main__":
asyncio.run(main())
9. Что НЕ кооперирует
import asyncio
# ❌ НЕПРАВИЛЬНО: CPU-bound операция блокирует другие задачи
async def cpu_intensive():
# Очень долгое вычисление
result = sum(i**2 for i in range(10**8)) # Нет await!
# Во время этой операции другие задачи ждут
return result
# ✓ ПРАВИЛЬНО: использовать executor для CPU-bound операций
async def cpu_intensive_correct():
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(
None, # Default executor
lambda: sum(i**2 for i in range(10**8))
) # ← Теперь выполняется в отдельном потоке
return result
async def main():
# Первый вариант (неправильный): task2 ждёт 2 секунды
# Второй вариант (правильный): task2 работает параллельно
results = await asyncio.gather(
cpu_intensive_correct(),
asyncio.sleep(2),
)
if __name__ == "__main__":
asyncio.run(main())
Сравнение: кооперативная vs. вытесняющая многозадачность
| Свойство | Кооперативная (asyncio) | Вытесняющая (threading) |
|---|---|---|
| Переключение | Явное (await) | Принудительное (ОС) |
| Контекст | Корутины | Потоки |
| Race conditions | Нет (одноточно) | Да (нужны locks) |
| Простота | Проще | Сложнее |
| IO операции | Отлично | Хорошо |
| CPU операции | Плохо | Хорошо |
| Overhead | Минимальный | Высокий |
Практический пример: веб-сервер
import asyncio
from aiohttp import web
async def handle_request(request):
# Каждый запрос — это отдельная корутина
user_id = request.match_info['user_id']
# Запросим данные из БД
user = await fetch_user(user_id) # ← КООПЕРИРУЕТ с БД
# Загружаем комментарии параллельно
comments = await fetch_comments(user_id) # ← КООПЕРИРУЕТ с БД
return web.json_response({
'user': user,
'comments': comments
})
async def main():
app = web.Application()
app.router.add_get('/users/{user_id}', handle_request)
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, 'localhost', 8080)
await site.start()
print("Server started")
await asyncio.sleep(float('inf')) # Бесконечное ожидание
if __name__ == "__main__":
asyncio.run(main())
В таком сервере:
- Один процесс обслуживает тысячи одновременных запросов
- Каждый запрос кооперирует с I/O операциями
- Очень эффективно использует ресурсы
Вывод
Кооперативная многозадачность кооперирует (уступает управление) при:
-
I/O операциях:
- Сетевые запросы (HTTP, TCP, UDP)
- Файловые операции
- Запросы в БД
-
Синхронизации:
- Ожидание событий
- Очереди
- Семафоры, блокировки
-
Задержках:
asyncio.sleep()- Таймеры
Главное преимущество: одноточность избегает race conditions, а асинхронность позволяет обслуживать тысячи операций параллельно.
Главное ограничение: CPU-bound операции блокируют всё, поэтому нужно использовать run_in_executor() для параллелизма.