Что выбрать в 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 для ожидания.