Получить доступ

Чем отличаются threading, multiprocessing и asyncio?

JuniorКонкурентностьasyncio
Ещё спрашивают так:
  • Когда использовать потоки, а когда процессы?
  • Что выбрать для параллельных HTTP-запросов?
  • Как распараллелить вычисления в Python?
Коротко

Потоки и asyncio — для ожидания ввода-вывода, процессы — для вычислений на нескольких ядрах.

  • threading: несколько потоков одного процесса, общая память, ограничены GIL
  • multiprocessing: отдельные процессы со своей памятью и своим GIL, данные передаются сериализацией
  • asyncio: один поток, кооперативная многозадачность, тысячи соединений дёшево

А ещё Для типового выбора удобен concurrent.futures: ThreadPoolExecutor / ProcessPoolExecutor

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

threading. Потоки внутри одного процесса делят память, поэтому обмен данными простой, но общее состояние надо защищать блокировками. Из-за GIL байткод выполняется одним потоком за раз, поэтому потоки подходят для I/O-bound задач: сетевые запросы, работа с диском, ожидание базы. Переключает потоки операционная система — в любой момент.

multiprocessing. Отдельные процессы с собственным интерпретатором — настоящий параллелизм на ядрах. Подходит для CPU-bound задач: расчёты, обработка изображений, парсинг больших файлов. Цена — запуск процессов дороже, память не общая, аргументы и результаты сериализуются (pickle).

asyncio. Один поток и цикл событий. Корутины сами отдают управление в точках await — пока одна ждёт ответа сети, работают другие. Тысячи одновременных соединений обходятся дёшево. Требует асинхронных библиотек (aiohttp, asyncpg); блокирующий вызов внутри корутины останавливает весь цикл.

Как выбирать:

  • много сетевых запросов, веб-сервер — asyncio (или потоки, если библиотеки синхронные);
  • немного параллельного ввода-вывода в синхронном коде — ThreadPoolExecutor;
  • тяжёлые вычисления — ProcessPoolExecutor.

Пример

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import urllib.request

def fetch(url):
    with urllib.request.urlopen(url, timeout=10) as r:
        return len(r.read())

urls = ["https://example.com"] * 10
with ThreadPoolExecutor(max_workers=10) as pool:      # I/O — потоки
    sizes = list(pool.map(fetch, urls))

def heavy(n):
    return sum(i * i for i in range(n))

if __name__ == "__main__":
    with ProcessPoolExecutor() as pool:                 # CPU — процессы
        results = list(pool.map(heavy, [10**7] * 4))

Что хотят услышать

  • Связь выбора с типом задачи: I/O-bound или CPU-bound.
  • Роль GIL для потоков.
  • Цена процессов: память, сериализация, запуск.
  • Модель asyncio: один поток, await, опасность блокирующих вызовов.

Частые ошибки

  • Распараллеливать вычисления потоками и ждать ускорения.
  • Вызывать time.sleep или синхронный requests внутри корутины.

Что спросят дальше

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем