Каковы различия между кооперативной и вытесняющей многозадачностью в Python

Ответ

Основное различие заключается в том, кто и когда принимает решение о переключении между задачами.

Кооперативная многозадачность (Cooperative Multitasking)

В этой модели задача сама добровольно передает управление другой задаче в явно указанном месте. В Python это реализуется с помощью asyncio.

  • Кто переключает: Сама программа (задача) через ключевое слово await.
  • Преимущества: Минимальные накладные расходы на переключение контекста, так как не требуется вмешательство ОС. Отсутствие гонки данных (race conditions) в рамках одного потока.
  • Недостатки: Одна долго выполняющаяся (блокирующая) задача, которая не передает управление, может «подвесить» весь цикл событий (event loop).

Пример (asyncio):

import asyncio

async def say_hello():
    print("Начало hello")
    await asyncio.sleep(1) # Явная передача управления
    print("Привет!")

async def say_world():
    print("Начало world")
    await asyncio.sleep(1) # Явная передача управления
    print("Мир!")

asyncio.run(asyncio.gather(say_hello(), say_world()))

Вытесняющая многозадачность (Preemptive Multitasking)

В этой модели операционная система (ОС) сама решает, когда прервать выполнение одной задачи и передать управление другой. В Python это реализуется с помощью потоков (threading).

  • Кто переключает: Операционная система.
  • Преимущества: Блокировка одной задачи не останавливает остальные. Возможность параллельного выполнения на нескольких ядрах процессора (с оговоркой на GIL).
  • Недостатки: Более высокие накладные расходы на переключение контекста. Требуется использование механизмов синхронизации (блокировки, семафоры) для предотвращения гонки данных.

Пример (threading):

import threading
import time

def print_number(num):
    time.sleep(1) # ОС может переключить поток в любой момент
    print(f"Поток {num}")

threads = [threading.Thread(target=print_number, args=(i,)) for i in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

Важно: В CPython из-за Global Interpreter Lock (GIL) вытесняющая многозадачность на потоках не дает реального параллелизма для задач, интенсивно использующих CPU. Однако она эффективна для I/O-bound задач (ожидание сети, диска), так как GIL освобождается на время ожидания.