← Назад к вопросам
Для вычислительной задачи лучше задействовать несколько процессов или несколько потоков
2.3 Middle🔥 221 комментариев
#Асинхронность и многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Для вычислительной задачи лучше задействовать несколько процессов или потоки
Коротко: ПРОЦЕССЫ для CPU-bound, ПОТОКИ для I/O-bound.
CPU-bound vs I/O-bound
# CPU-bound: вычисления, обработка данных
def cpu_work(n):
return sum(i*i for i in range(n))
cpu_work(10**8) # Медленно, требует CPU
# I/O-bound: сетевые запросы, файловые операции
import requests
response = requests.get('https://example.com') # Быстро ждёшь ответ
Потоки (threads)
Потоки используют одно ядро из-за GIL (Global Interpreter Lock).
import threading
import time
def cpu_work():
return sum(i*i for i in range(10**8))
start = time.time()
t1 = threading.Thread(target=cpu_work)
t2 = threading.Thread(target=cpu_work)
t1.start(); t2.start()
t1.join(); t2.join()
print(f"Threads: {time.time() - start:.1f}s") # 20s (медленнее!)
Почему медленно:
- GIL позволяет работать только одному потоку одновременно
- Переключение контекста добавляет overhead
- Результат: медленнее чем последовательно!
Процессы (multiprocessing)
Процессы используют несколько ядер (нет GIL).
from multiprocessing import Process
import time
def cpu_work():
return sum(i*i for i in range(10**8))
start = time.time()
p1 = Process(target=cpu_work)
p2 = Process(target=cpu_work)
p1.start(); p2.start()
p1.join(); p2.join()
print(f"Processes: {time.time() - start:.1f}s") # 10s (в 2x быстрее!)
Сравнение
Задача: sum(i*i for i in range(10**8))
Последовательно: 10s
1 поток: 10s (GIL)
2 потока: 20s (GIL + overhead)
2 процесса: 5s (настоящий параллелизм!)
CPU-bound: используй процессы
from multiprocessing import Pool
def calculate(n):
return sum(i*i for i in range(10**7))
# Используй Pool
with Pool(4) as pool:
results = pool.map(calculate, [1, 2, 3, 4, 5, 6, 7, 8])
# 4 процесса параллельно на 4-ядерной машине
I/O-bound: используй потоки или asyncio
import threading
import requests
def fetch(url):
return requests.get(url).text
# Потоки
threads = [threading.Thread(target=fetch, args=(url,)) for url in urls]
for t in threads: t.start()
# ИЛИ asyncio (лучше)
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
await asyncio.gather(*tasks)
Практические примеры
Пример 1: Обработка больших файлов (CPU-bound)
from multiprocessing import Pool
import numpy as np
def process_chunk(chunk):
# Обработка (медленно)
return np.sum(chunk ** 2)
# Читаем большой файл чанками
chunks = read_file_in_chunks('huge_data.bin', chunk_size=10**6)
# Используем несколько процессов
with Pool(4) as pool:
results = pool.map(process_chunk, chunks)
total = sum(results)
Пример 2: Скачивание файлов (I/O-bound)
import concurrent.futures
import requests
def download(url):
response = requests.get(url)
return len(response.content)
urls = ['http://example.com/file1.zip', ...]
# Используем потоки
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
results = executor.map(download, urls)
Пример 3: Веб-скрейпинг (I/O-bound)
import asyncio
import aiohttp
async def scrape(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
urls = ['http://example.com/page1', ...]
# asyncio для IO-bound
results = asyncio.run(
asyncio.gather(*[scrape(url) for url in urls])
)
Проблемы каждого подхода
Потоки (threads)
Плюсы:
- Легко использовать
- Хорошо для I/O
- Меньше overhead чем процессы
Минусы:
- GIL убивает CPU-bound
- Race conditions
- Deadlocks возможны
Процессы (multiprocessing)
Плюсы:
- Истинный параллелизм для CPU
- Нет GIL
- Масштабируется на многоядрах
Минусы:
- Медленнее запускаются (форк процесса)
- Больше памяти (каждый процесс - отдельный Python)
- Сложнее share данные
- IPC overhead
Asyncio
Плюсы:
- Очень быстро для I/O
- Низкий overhead
- Масштабируется на тысячи connections
Минусы:
- Не работает для CPU-bound
- Требует async/await везде
- Понять сложнее
Когда использовать что
CPU-bound (вычисления):
├─ Малое количество: Использовать процессы (multiprocessing)
├─ Большое: Использовать Cython или NumPy на C
└─ Критичное: Использовать Rust/C с Python биндингами
I/O-bound (сетевые операции):
├─ Быстрое решение: asyncio
├─ Интеграция с blocking code: потоки
└─ Микросервисы: потоки/asyncio на разных сервисах
Реальный пример: выбор технологии
# Задача: Обработать 1000 изображений
# Вариант 1: Потоки (медленно!)
with ThreadPoolExecutor(max_workers=10) as executor:
executor.map(process_image, images)
# Время: 100s (GIL блокирует)
# Вариант 2: Процессы (быстро!)
with Pool(4) as pool:
pool.map(process_image, images)
# Время: 25s (4x ядра работают)
# Вариант 3: GPU (если есть)
# Время: 5s (специализированное оборудование)
Как я выбираю
-
Профилирую задачу
- Сколько времени на CPU vs I/O?
-
Если 90% CPU:
- Используй multiprocessing
- Или Cython/NumPy
-
Если 90% I/O:
- Используй asyncio
- Или потоки если нет выбора
-
Если смешанное:
- Комбинирую подходы
- Asyncio для I/O + процессы для CPU
Инструменты для профилирования
import cProfile
import pstats
# Профилирование
cProfile.run('my_function()')
# Или более детально
from line_profiler import profile
@profile
def slow_function():
pass
Заключение
- CPU-bound → многопроцессность
- I/O-bound → asyncio или потоки
- Профилируй перед оптимизацией
- GIL убивает CPU-bound потоки
- asyncio лучше для I/O чем потоки
- Иногда Cython/NumPy лучше чем multiprocessing