Какие основные подходы к конкурентному и параллельному выполнению кода есть в Python?

Ответ

В Python существует три основных подхода для организации конкурентного и параллельного выполнения кода, каждый из которых решает свой класс задач.

  1. Многопоточность (threading)

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

    def io_task():

    Симуляция сетевого запроса

    print("Поток начал работу")

    thread = threading.Thread(target=io_task) thread.start() thread.join()

  2. Многопроцессорность (multiprocessing)

    • Принцип: Запускаются отдельные процессы, каждый со своим интерпретатором Python и своим экземпляром GIL. Процессы не разделяют память по умолчанию.
    • Преимущество: Позволяет достичь настоящего параллелизма и полностью утилизировать все ядра процессора.
    • Применение: Эффективен для CPU-bound задач (математические вычисления, обработка больших объемов данных).
      
      from multiprocessing import Process

    def cpu_task():

    Симуляция вычислений

    sum(i * i for i in range(10**7))

    process = Process(target=cpu_task) process.start() process.join()

  3. Асинхронность (asyncio)

    • Принцип: Конкурентное выполнение в одном потоке с помощью событийного цикла (event loop) и корутин (async/await).
    • Преимущество: Очень низкие накладные расходы на переключение контекста по сравнению с потоками. Позволяет эффективно обрабатывать тысячи одновременных I/O-операций.
    • Применение: Высокопроизводительные I/O-bound задачи, особенно сетевые приложения (веб-серверы, клиенты API, боты).
      
      import asyncio

    async def async_io_task():

    Асинхронная I/O операция

    await asyncio.sleep(1)
    print("Асинхронная задача завершена")

    asyncio.run(async_io_task())

Подход Лучше для... Обход GIL Основная единица
threading I/O-bound Нет Поток
multiprocessing CPU-bound Да Процесс
asyncio I/O-bound (масштабируемое) Нет Задача (Task)

Ответ 18+ 🔞

Слушай, а вот эти твои питоновские подходы к параллельному выполнению — это ж, блядь, как три разных способа заставить комп работать, пока ты спишь. Разберём, как есть, без соплей.

1. Многопоточность (threading) Представь, что у тебя один повар на кухне (это GIL, сука), но он может готовить несколько блюд, переключаясь между ними. Пока одно тушится (I/O-bound задача), он начинает резать овощи для другого. Но если ему дать пять одинаковых задач посчитать интегралы (CPU-bound) — он всё равно будет делать их по очереди, один хуй. Так что потоки — это для ожидания: сети, диска, ответа от сервера. Всё, что связано с простоем.

import threading

def io_task():
    # Симуляция сетевого запроса
    print("Поток начал работу")

thread = threading.Thread(target=io_task)
thread.start()
thread.join()

2. Многопроцессорность (multiprocessing) А вот это уже, ёпта, серьёзно. Берёшь и нанимаешь ещё поваров — каждый со своей кухней, своими ножами и своим GIL, который ему похуй. Они могут одновременно жарить, парить и ебашить вычисления. Память, правда, у каждого своя, общаться через забор — но зато CPU-bound задачи разнесут влёт. Хочешь считать числа, обрабатывать изображения — вот твой выбор.

from multiprocessing import Process

def cpu_task():
    # Симуляция вычислений
    sum(i * i for i in range(10**7))

process = Process(target=cpu_task)
process.start()
process.join()

3. Асинхронность (asyncio) А это, блядь, магия. Один повар, но он не ждёт, пока суп закипит. Он поставил кастрюлю, сразу побежал резать лук, потом вернулся, помешал, снова побежал. Всё через async/await. Нагрузка на переключения — почти ноль. Если у тебя тысяча сетевых запросов, веб-сокетов или какой-нибудь API-шлюз — это твой священный грааль. Но только для I/O, на вычислениях он просто будет метаться, как угорелый.

import asyncio

async def async_io_task():
    # Асинхронная I/O операция
    await asyncio.sleep(1)
    print("Асинхронная задача завершена")

asyncio.run(async_io_task())

А теперь, сука, табличка, чтобы вообще всё встало на свои места:

Подход Лучше для... Обход GIL Основная единица
threading I/O-bound Нет Поток
multiprocessing CPU-bound Да Процесс
asyncio I/O-bound (масштабируемое) Нет Задача (Task)

Короче, выбирай по задаче: ждёшь — потоки, считаешь — процессы, ждёшь овердохуища всего сразу — асинхронность. И не путай, а то получишь пиздец вместо производительности.