Ответ
Для тяжелых, 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 и не парься.