← Назад к вопросам

С чем кооперируется кооперативная многозадачность

2.3 Middle🔥 171 комментариев
#Архитектура и паттерны#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

С чем кооперируется кооперативная многозадачность

Отличный вопрос! Давайте разберемся, как работает кооперативная многозадачность:

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 операциями
  • Очень эффективно использует ресурсы

Вывод

Кооперативная многозадачность кооперирует (уступает управление) при:

  1. I/O операциях:

    • Сетевые запросы (HTTP, TCP, UDP)
    • Файловые операции
    • Запросы в БД
  2. Синхронизации:

    • Ожидание событий
    • Очереди
    • Семафоры, блокировки
  3. Задержках:

    • asyncio.sleep()
    • Таймеры

Главное преимущество: одноточность избегает race conditions, а асинхронность позволяет обслуживать тысячи операций параллельно.

Главное ограничение: CPU-bound операции блокируют всё, поэтому нужно использовать run_in_executor() для параллелизма.

С чем кооперируется кооперативная многозадачность | PrepBro