В чём разница между многопоточностью и многопроцессорностью в Python?

Ответ

В Python эти концепции реализованы с учётом Global Interpreter Lock (GIL), что накладывает ключевые ограничения.

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

  • Что это: Запуск нескольких потоков выполнения в рамках одного процесса. Потоки разделяют общую память (глобальные переменные, структуры данных).
  • В Python (из-за GIL): GIL позволяет выполняться только одному потоку Python в любой момент времени, даже на многоядерном CPU. Это делает чистые потоки Python неэффективными для CPU-задач (вычисления, обработка изображений).
  • Для чего использовать: Для I/O-bound задач, где потоки большую часть времени ожидают (сеть, диск, ввод-вывод). Пока один поток ждёт, GIL может быть передан другому.
    
    import threading
    import requests

def download_url(url): response = requests.get(url) print(f"Downloaded {len(response.content)} bytes from {url}")

urls = ["https://example.com/1", "https://example.com/2"] threads = [] for url in urls: thread = threading.Thread(target=download_url, args=(url,)) thread.start() threads.append(thread) for t in threads: t.join() # I/O-bound задача хорошо распараллеливается потоками


### Многопроцессорность (`multiprocessing`)
*   **Что это:** Запуск нескольких **независимых процессов**, каждый со своим интерпретатором Python и памятью. Обмен данными требует специальных механизмов (очереди, каналы, shared memory).
*   **В Python (обход GIL):** Каждый процесс имеет свой GIL и может выполняться на отдельном ядре CPU. Это делает многопроцессорность **эффективной для CPU-задач**.
*   **Для чего использовать:** Для **CPU-bound задач** (математические расчёты, обработка данных).
```python
import multiprocessing
import math

def calculate_factorial(n):
    print(f"Calculating {n}!")
    return math.factorial(n)

if __name__ == '__main__':
    numbers = [1000, 2000, 3000]
    with multiprocessing.Pool(processes=3) as pool:
        results = pool.map(calculate_factorial, numbers) # CPU-bound задача
    print(results)
Сводная таблица: Критерий Многопоточность (threading) Многопроцессорность (multiprocessing)
Единица выполнения Поток (Thread) Процесс (Process)
Память Общая Раздельная (требует IPC)
GIL в Python Один на все потоки (ограничивает CPU) Свой GIL на каждый процесс
Идеально для I/O-bound задачи (сеть, файлы) CPU-bound задачи (вычисления)
Накладные расходы Низкие Высокие (запуск процесса, IPC)

Ответ 18+ 🔞

А, ну это же классика, ебать мои старые костыли! Сидишь такой, пишешь на Python, думаешь — щас я на все ядра процессора навалю вычислений, и будет мне счастье. Ан нет, хуй с горы! Выходит на сцену этот ваш GIL (Global Interpreter Lock) — такой пидарас шерстяной, который стоит у тебя в интерпретаторе и орет: «Стоп, мудила! Только один поток Python-кода в один момент времени! Не суйся все сразу!».

И вот тут начинается самое интересное, чувак. Ты стоишь на распутье, как богатырь, и думаешь: брать threading или multiprocessing? А выбор-то, блядь, зависит от того, чем твоя программа занимается — ждёт или пашет.

Многопоточность (threading) — для тех, кто любит ждать

  • Что это такое: Это когда ты в одном процессе запускаешь кучу потоков. Они все видят одну и ту же память — глобальные переменные, списки, словари. Удобно, да? Но это же и минус, потому что если неаккуратно, то один поток нагадит в общие данные, а другой словит исключение и скажет «чё за хуйня?».
  • В Python (спасибо, GIL, ёпта): Из-за этого самого GIL, даже если у тебя 16 ядер, твои потоки Python-кода будут работать последовательно, как в очереди за колбасой в девяностые. Один пашет, остальные стоят и смотрят. Поэтому для тяжелых вычислений — полный пиздец, нихуя не ускорится.
  • Зачем тогда это надо: А вот для задач, где программа тупо ждёт — это идеально. Скачать сто картинок по сети, прочитать десять файлов с диска, отправить кучу запросов в базу данных. Пока один поток упёрся лбом в ожидание ответа от сервера, GIL отпускает его и даёт поработать другому. И так по кругу. I/O-bound задачи — их вотчина.
import threading
import requests

def download_url(url):
    # Тут поток будет большую часть времени ждать ответа от интернета
    response = requests.get(url)
    print(f"Downloaded {len(response.content)} bytes from {url}")

urls = ["https://example.com/1", "https://example.com/2"]
threads = []
for url in urls:
    thread = threading.Thread(target=download_url, args=(url,))
    thread.start()
    threads.append(thread)
for t in threads:
    t.join()  # I/O-bound задача — тут потоки рулят, пизда рулю!

Многопроцессорность (multiprocessing) — для настоящих работяг

  • Что это такое: А это уже серьёзно. Ты запускаешь несколько отдельных процессов. У каждого — свой собственный интерпретатор Python, своя память. Это как несколько независимых программ, которые договорились вместе работать. Чтобы общаться, им нужно через специальные шлюзы данные передавать — очереди, каналы. Заморочек больше.
  • В Python (а вот это уже хитро, жопа): У каждого процесса — свой собственный GIL. И вот они уже могут разбежаться по разным ядрам твоего процессора и пахать по-настоящему параллельно. GIL одного процесса не мешает GIL другого. Красота!
  • Зачем это надо: Когда нужно считать, а не ждать. Обработать терабайты данных, перемножить гигантские матрицы, обсчитать физическую модель. CPU-bound задачи — вот где эти ребята выдают овердохуища производительности.
import multiprocessing
import math

def calculate_factorial(n):
    # А вот тут уже процесс будет грузить ядро по полной
    print(f"Calculating {n}!")
    return math.factorial(n)

if __name__ == '__main__':
    numbers = [1000, 2000, 3000]
    with multiprocessing.Pool(processes=3) as pool:
        results = pool.map(calculate_factorial, numbers) # CPU-bound задача — тут процессы боги
    print(results)

Коротко, чтоб не ебать мозг (шпаргалка)

Критерий Многопоточность (threading) Многопроцессорность (multiprocessing)
Кто работает Потоки в одном процессе Отдельные процессы
Память Общая (легко накосячить) Раздельная (надо специально данные передавать)
Главный враг (GIL) Один на всех, всех и душит У каждого свой, все свободны
Идеальная работа Ждать (I/O-bound: сеть, диск) Считать (CPU-bound: вычисления)
Цена вопроса Дёшево Дорого (запуск процесса — это вам не хуй с винтом)

Короче, запомни, чувак: ждёшь — бери потоки, считаешь — бери процессы. А если перепутаешь — сам от себя охуеешь, когда твоя 16-ядерная махина будет грузить одно ядро на 100%, а остальные 15 мирно спать.