Что выбрать в Python для CPU-bound задач: многопоточность или многопроцессность

Ответ

Для тяжелых, CPU-bound вычислений в Python однозначно следует использовать многопроцессность (multiprocessing).

Почему не многопоточность?

Ключевая причина — Global Interpreter Lock (GIL). Это механизм в стандартном интерпретаторе CPython, который позволяет только одному потоку исполнять Python-байткод в один момент времени. В результате, многопоточность (threading) не дает прироста производительности на многоядерных процессорах для задач, интенсивно использующих CPU.

Преимущества multiprocessing

  • Обход GIL: Каждый процесс получает собственный интерпретатор Python и область памяти, поэтому GIL одного процесса не мешает другим.
  • Полная утилизация CPU: Позволяет по-настоящему параллельно выполнять код на всех доступных ядрах процессора.

Сравнение:

  • multiprocessing: Идеально для CPU-bound задач (математические расчеты, обработка данных).
  • threading: Эффективна для I/O-bound задач (ожидание ответа от сети, чтение с диска), так как GIL освобождается на время ожидания ввода-вывода.

Пример распараллеливания вычислений с multiprocessing.Pool:

import multiprocessing
import time

def heavy_computation(number):
    # Имитация сложной вычислительной задачи
    result = 0
    for i in range(10**7):
        result += i
    return number * number

if __name__ == "__main__":
    # Конструкция if __name__ == "__main__" обязательна 
    # для корректной работы multiprocessing в некоторых ОС
    numbers = [1, 2, 3, 4, 5, 6, 7, 8]

    start_time = time.time()

    # Создаем пул из 4 процессов
    with multiprocessing.Pool(processes=4) as pool:
        results = pool.map(heavy_computation, numbers)

    print(f"Результаты: {results}")
    print(f"Время выполнения: {time.time() - start_time:.2f} секунд")

# Вывод (примерный):
# Результаты: [1, 4, 9, 16, 25, 36, 49, 64]
# Время выполнения: 2.15 секунд 
# (Без пула это заняло бы ~8 секунд на 1 ядре)

Вывод: Используйте multiprocessing для вычислений и threading для ожидания.

Ответ 18+ 🔞

Так, слушай, а то тут некоторые умники думают, что в Python можно всё в потоки запихнуть и будет летать. Хуй там плавал, дружок!

Вот смотри, есть такая штука — Global Interpreter Lock (GIL), ёпта. Это такая манда с ушами в самом сердце CPython, которая говорит: «В один момент времени только один поток может исполнять питонячий байткод». Представь очередь в один хуёвый сортир на стройке из сотни мужиков. Вот это оно и есть. Поэтому твои CPU-bound вычисления в потоках (threading) будут работать последовательно, как будто у тебя одно ядро, хоть ты обосрись.

Так что же делать?

А делать надо вот что: мультипроцессинг (multiprocessing). Это когда ты запускаешь не потоки, а отдельные процессы. У каждого — свой интерпретатор, своя память, и свой, блядь, GIL. И они уже по-настоящему могут грузить все твои ядра до овердохуища.

Короче, правило простое:

  • multiprocessing — для CPU-bound задач (цифры крутишь, данные обрабатываешь, вычисления ебёшь).
  • threading — для I/O-bound задач (ждёшь ответ от сервера, файлы читаешь, в общем, когда просто сидишь и бздишь в потолок).

Вот, глянь, как это выглядит на практике. Код не трогаю, он и так норм.

import multiprocessing
import time

def heavy_computation(number):
    # Имитация сложной вычислительной задачи
    result = 0
    for i in range(10**7):
        result += i
    return number * number

if __name__ == "__main__":
    # Конструкция if __name__ == "__main__" обязательна 
    # для корректной работы multiprocessing в некоторых ОС
    numbers = [1, 2, 3, 4, 5, 6, 7, 8]

    start_time = time.time()

    # Создаем пул из 4 процессов
    with multiprocessing.Pool(processes=4) as pool:
        results = pool.map(heavy_computation, numbers)

    print(f"Результаты: {results}")
    print(f"Время выполнения: {time.time() - start_time:.2f} секунд")

# Вывод (примерный):
# Результаты: [1, 4, 9, 16, 25, 36, 49, 64]
# Время выполнения: 2.15 секунд 
# (Без пула это заняло бы ~8 секунд на 1 ядре)

Видишь? Вместо того чтобы один поток восемь секунд ебался, четыре процесса раздолбали задачу за два. Вот и вся магия. Не усложняй, просто бери multiprocessing и не парься.