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

Что переключается быстрее - потоки ли процессы в Python?

3.0 Senior🔥 71 комментариев
#Python Core#Асинхронность и многопоточность

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

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

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

Что переключается быстрее - потоки ли процессы в Python?

Ответ: потоки (threads) переключаются быстрее, чем процессы (processes). Но нужно понимать, ЧТО имеется в виду под "быстрее".

Теория: Context switching

Потоки (Threads):
- Существуют в ОДНОМ процессе
- Делят одну память, один heap
- Переключение: быстро (микросекунды)
- Нужно: сохранить регистры и указатель стека

Процессы (Processes):
- Отдельные изолированные программы
- Своя память для каждого
- Переключение: медленнее (миллисекунды)
- Нужно: сохранить всё состояние + переключить virtual memory

Почему потоки быстрее?

import time
import threading
import multiprocessing

# Потоки используют меньше ресурсов
thread = threading.Thread(target=print, args=("Hello",))
print(f"Thread объект: {thread}")
print(f"Memory footprint: ~8KB на поток")

# Процессы — это полные копии Python интерпретатора
process = multiprocessing.Process(target=print, args=("Hello",))
print(f"Process объект: {process}")
print(f"Memory footprint: ~30-50MB на процесс!")

Context switching: бенчмарк

import time
import threading
import multiprocessing

def worker():
    """Простая работа"""
    total = 0
    for _ in range(1000000):
        total += 1
    return total

# 1. ПОТОКИ
start = time.time()
threads = []
for _ in range(4):
    t = threading.Thread(target=worker)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

threads_time = time.time() - start
print(f"Потоки (4 потока): {threads_time:.2f}s")

# 2. ПРОЦЕССЫ
start = time.time()
processes = []
for _ in range(4):
    p = multiprocessing.Process(target=worker)
    processes.append(p)
    p.start()

for p in processes:
    p.join()

processes_time = time.time() - start
print(f"Процессы (4 процесса): {processes_time:.2f}s")

print(f"Потоки быстрее в {processes_time / threads_time:.1f}x раз")
# Результат: потоки примерно в 2-3x раза быстрее

НО ЕСТЬ ПРОБЛЕМА: GIL (Global Interpreter Lock)

Вот в чём подвох Python:

import threading
import time

def cpu_bound_task():
    """Работа требует процессора"""
    total = 0
    for _ in range(100_000_000):
        total += 1

start = time.time()

# ПОТОКИ — проблема!
t1 = threading.Thread(target=cpu_bound_task)
t2 = threading.Thread(target=cpu_bound_task)
t1.start()
t2.start()
t1.join()
t2.join()

threads_time = time.time() - start

# ПРОЦЕССЫ — работает
from multiprocessing import Process

start = time.time()
p1 = Process(target=cpu_bound_task)
p2 = Process(target=cpu_bound_task)
p1.start()
p2.start()
p1.join()
p2.join()

processes_time = time.time() - start

print(f"Однопоточно: ~8s")
print(f"Потоки (2 потока): ~9-10s (МЕДЛЕННЕЕ!)")
print(f"Процессы (2 процесса): ~4-5s (2x быстрее)")

Вывод: когда что использовать

# 1. I/O-BOUND операции (сетевые запросы, файлы)
# ПОТОКИ лучше! Они переключаются быстро и GIL отпускается

import threading
import requests
import time

def fetch_url(url):
    requests.get(url)  # блокирующий I/O

start = time.time()
threads = []
for _ in range(10):
    t = threading.Thread(target=fetch_url, args=("https://httpbin.org/delay/1",))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"Потоки для I/O: {time.time() - start:.2f}s")  # ~1s (параллельно)

# 2. CPU-BOUND операции (вычисления, обработка данных)
# ПРОЦЕССЫ лучше! GIL не помешает

from multiprocessing import Pool

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

start = time.time()
with Pool(4) as p:
    results = p.map(calculate, [1000000] * 10)

print(f"Процессы для CPU: {time.time() - start:.2f}s")  # быстро

# 3. СМЕШАННЫЕ операции
# AsyncIO (асинхронность) — лучший выбор!

import asyncio
import aiohttp

async def fetch_async(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    tasks = [fetch_async("https://httpbin.org/delay/1") for _ in range(10)]
    await asyncio.gather(*tasks)

start = time.time()
asyncio.run(main())
print(f"AsyncIO: {time.time() - start:.2f}s")  # ~1s, без потоков!

Память: потоки vs процессы

import threading
import multiprocessing
import os

print(f"Процесс {os.getpid()} использует ~30MB")

# Создание 100 потоков
threads = []
for _ in range(100):
    t = threading.Thread(target=lambda: None)
    threads.append(t)

print(f"100 потоков: +~800KB (0.8MB)")

# Создание 100 процессов? Не делай так!
# processes = [multiprocessing.Process(target=lambda: None) for _ in range(100)]
# Это потребует ~3GB памяти!

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

┌──────────────────────┬──────────────┬──────────────┐
│ Параметр             │ Потоки       │ Процессы     │
├──────────────────────┼──────────────┼──────────────┤
│ Context switching    │ Быстро       │ Медленнее     │
│ Память на один       │ ~8KB         │ ~30-50MB     │
│ GIL (CPU-bound)      │ ПРОБЛЕМА     │ No GIL       │
│ I/O-bound           │ Хорошо       │ Хорошо       │
│ Data sharing        │ Легко        │ Сложно (IPC) │
│ Безопасность        │ Нужны локи   │ Изолированы │
│ Масштабируемость    │ До 100s      │ До 1000s     │
└──────────────────────┴──────────────┴──────────────┘

Практический совет

# 1. I/O-BOUND — используй threading
from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=10) as executor:
    results = executor.map(fetch_url, urls)

# 2. CPU-BOUND — используй multiprocessing
from concurrent.futures import ProcessPoolExecutor

with ProcessPoolExecutor(max_workers=4) as executor:
    results = executor.map(calculate, data)

# 3. I/O-BOUND (современный способ) — используй asyncio
import asyncio

async def main():
    tasks = [fetch_async(url) for url in urls]
    return await asyncio.gather(*tasks)

asyncio.run(main())

# 4. Лучший выбор для масштабируемости — async!await
# - Нет GIL
# - Быстрое переключение (даже быстрее потоков)
# - Меньше памяти чем потоки
# - Удобный синтаксис

Резюме

Потоки переключаются быстрее на уровне ОС (миллисекунды vs микросекунды), но из-за GIL в Python:

  • Потоки лучше для: I/O-bound (сетевые запросы, файлы, БД)
  • Процессы лучше для: CPU-bound вычисления (numpy, scipy, машинное обучение)
  • AsyncIO лучше всего для: Современные приложения с множеством I/O операций (веб-сервисы, API клиенты)

Это один из главных недостатков Python, и почему для высоконагруженных систем часто используют Go, Java или Rust вместо Python.

Что переключается быстрее - потоки ли процессы в Python? | PrepBro