← Назад к вопросам

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

2.2 Middle🔥 221 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Многопроцессность vs Многопоточность в Python

Это фундаментальное различие в моделях конкурентности, и для Python оно имеет особенное значение из-за GIL (Global Interpreter Lock).

Основные определения

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

Несколько потоков работают в одном процессе, совместно используя одно адресное пространство памяти. Контекстное переключение между потоками происходит очень быстро (на уровне операционной системы).

import threading
import time

def worker(name):
    for i in range(3):
        print(f"Worker {name}: {i}")
        time.sleep(1)

# Создание потоков
threads = []
for i in range(2):
    t = threading.Thread(target=worker, args=(f"Thread-{i}",))
    threads.append(t)
    t.start()

# Ожидание завершения
for t in threads:
    t.join()

print("Все потоки завершены")

Потоки работают в одном процессе, поэтому они могут легко делиться данными:

import threading

shared_value = 0
lock = threading.Lock()

def increment():
    global shared_value
    for _ in range(1000000):
        with lock:
            shared_value += 1

t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
t1.join()
t2.join()

print(f"Result: {shared_value}")  # 2000000

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

Несколько независимых процессов с собственным адресным пространством. Каждый процесс — полностью отдельный экземпляр интерпретатора Python с собственным GIL.

import multiprocessing
import time

def worker(name):
    for i in range(3):
        print(f"Worker {name}: {i}")
        time.sleep(1)

if __name__ == '__main__':
    processes = []
    for i in range(2):
        p = multiprocessing.Process(target=worker, args=(f"Process-{i}",))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()
    
    print("Все процессы завершены")

Даже разделяемые переменные требуют специальных механизмов (Queue, Pipe, Manager):

import multiprocessing

def worker(q):
    q.put(42)  # Отправить значение в очередь

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    p = multiprocessing.Process(target=worker, args=(queue,))
    p.start()
    
    value = queue.get()  # Получить значение
    print(f"Got: {value}")
    
    p.join()

Сравнительная таблица

АспектМногопоточностьМногопроцессность
Адресное пространствоОбщееОтдельное у каждого
ПамятьЭкономнаТребует больше памяти
СозданиеБыстро (< 1мс)Медленно (10мс+)
СинхронизацияЛегко (shared memory)Сложно (IPC)
GIL в PythonМЕШАЕТ для CPU-boundНЕ МЕШАЕТ — обходит GIL
ОтладкаСложнее (race conditions)Проще (изоляция)
НадёжностьОдна ошибка — весь процессКрах одного — остальные живы

Почему в Python это так важно: Global Interpreter Lock (GIL)

Python (CPython) имеет мьютекс GIL, который позволяет только одному потоку одновременно выполнять код на Python. Это означает, что многопоточность НЕ даёт параллелизма для CPU-bound задач.

CPU-bound пример (вычисления):

import threading
import time

def cpu_intensive():
    total = 0
    for i in range(100000000):
        total += i
    return total

# Однопоточный вариант
start = time.time()
cpu_intensive()
cpu_intensive()
print(f"Sequential: {time.time() - start:.2f}s")  # ~3.2s

# Многопоточный вариант (из-за GIL работает медленнее!)
start = time.time()
t1 = threading.Thread(target=cpu_intensive)
t2 = threading.Thread(target=cpu_intensive)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Threading: {time.time() - start:.2f}s")  # ~4.0s (МЕДЛЕННЕЕ!)

# Многопроцессный вариант (настоящий параллелизм)
start = time.time()
p1 = multiprocessing.Process(target=cpu_intensive)
p2 = multiprocessing.Process(target=cpu_intensive)
p1.start()
p2.start()
p1.join()
p2.join()
print(f"Multiprocessing: {time.time() - start:.2f}s")  # ~1.8s (в 2 раза быстрее!)

I/O-bound пример (ввод-вывод):

import threading
import asyncio
import httpx
import time

# Многопоточный вариант (работает нормально)
start = time.time()
threads = []

for _ in range(10):
    t = threading.Thread(target=lambda: httpx.get("https://api.github.com"))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"Threading (I/O): {time.time() - start:.2f}s")  # ~1.5s (хорошо!)

# Асинхронный вариант (ещё лучше)
async def fetch_all():
    async with httpx.AsyncClient() as client:
        tasks = [client.get("https://api.github.com") for _ in range(10)]
        await asyncio.gather(*tasks)

start = time.time()
asyncio.run(fetch_all())
print(f"Async (I/O): {time.time() - start:.2f}s")  # ~1.2s (ещё быстрее)

Практические рекомендации

Используй многопоточность для:

  • I/O-bound задач (сетевые запросы, работа с файлами)
  • Отзывчивого пользовательского интерфейса (UI остаётся ответственным)
  • Когда нужно делиться памятью между параллельными задачами
from concurrent.futures import ThreadPoolExecutor
import requests

with ThreadPoolExecutor(max_workers=10) as executor:
    urls = [f"https://api.github.com/users/user{i}" for i in range(100)]
    results = executor.map(requests.get, urls)

Используй многопроцессность для:

  • CPU-bound задач (вычисления, обработка данных)
  • Когда нужна надёжность (крах одного процесса не влияет на остальные)
  • Когда требуется истинный параллелизм на нескольких ядрах
from concurrent.futures import ProcessPoolExecutor

def heavy_computation(n):
    return sum(i*i for i in range(n))

with ProcessPoolExecutor(max_workers=4) as executor:
    results = executor.map(heavy_computation, [100000000] * 4)

Используй асинхронность для:

  • Максимально масштабируемых I/O операций (десятки тысяч одновременных соединений)
  • Микросервисов и API серверов
  • Когда нужно обработать много параллельных задач эффективно
import asyncio
import aiohttp

async def fetch_all(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        return await asyncio.gather(*tasks)

asyncio.run(fetch_all([...]))

Выводы

  1. GIL в Python делает многопоточность неэффективной для CPU-bound задач
  2. Для I/O-bound задач многопоточность и асинхронность работают хорошо
  3. Многопроцессность обходит GIL, но требует больше памяти и сложнее в синхронизации
  4. Асинхронность (async/await) — золотой стандарт для I/O-bound приложений в Python