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

В чём разница между threading и multiprocessing в Python?

2.0 Middle🔥 111 комментариев
#DevOps и инфраструктура#Django

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

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

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

Решение: Threading vs Multiprocessing в Python

Это один из ключевых вопросов про параллелизм в Python. Threading и multiprocessing решают разные проблемы и имеют совершенно разные характеристики.

Основные Различия

АспектThreadingMultiprocessing
ИзоляцияОбщая памятьОтдельные процессы
GILОграничен GIL (Global Interpreter Lock)Не ограничен GIL
Параллелизм CPUНет (только на I/O)Да (истинный параллелизм)
OverheadНизкийВысокий
СинхронизацияСложная (использовать Lock, Semaphore)Проще (separate memory)
Shared dataЛегко (общая память, но опасно)Сложно (нужны очереди, pipes)
Скорость стартаБыстроМедленнее
ОтладкаСложнееПроще

Что такое GIL?

GIL (Global Interpreter Lock) — это мьютекс, который не позволяет нескольким потокам одновременно выполнять Python bytecode.

import threading
import time

def cpu_bound_task():
    """CPU-интенсивная задача"""
    total = 0
    for i in range(100_000_000):
        total += i
    return total

# Однопоточное выполнение
start = time.time()
cpu_bound_task()
cpu_bound_task()
serial_time = time.time() - start
print(f"Serial: {serial_time:.2f}s")

# Многопоточное выполнение (из-за GIL будет МЕДЛЕННЕЕ!)
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()
thread_time = time.time() - start
print(f"Threading: {thread_time:.2f}s")

Вывод:

Serial: ~10s
Threading: ~12s (медленнее из-за overhead)

Когда Использовать Threading

Threading полезен для I/O-bound операций (сеть, файлы, БД):

import threading
import requests
import time

def download(url):
    """Загрузить одну страницу"""
    response = requests.get(url)
    print(f"Downloaded {len(response.text)} bytes from {url}")

urls = [
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
]

# Однопоточно: ~3 секунды
start = time.time()
for url in urls:
    download(url)
print(f"Serial: {time.time() - start:.2f}s")

# Многопоточно: ~1 секунда (параллельные I/O)
start = time.time()
threads = []
for url in urls:
    t = threading.Thread(target=download, args=(url,))
    t.start()
    threads.append(t)
for t in threads:
    t.join()
print(f"Threading: {time.time() - start:.2f}s")

Когда Использовать Multiprocessing

Multiprocessing нужен для CPU-bound операций (вычисления, обработка данных):

import multiprocessing
import time

def cpu_bound_task(n):
    """CPU-интенсивная задача"""
    total = 0
    for i in range(n):
        total += i
    return total

if __name__ == '__main__':
    # Однопроцессно: ~10 сек
    start = time.time()
    cpu_bound_task(100_000_000)
    cpu_bound_task(100_000_000)
    serial_time = time.time() - start
    print(f"Serial: {serial_time:.2f}s")
    
    # Многопроцессно: ~5 сек (на 2-ядерной машине)
    start = time.time()
    with multiprocessing.Pool(2) as pool:
        results = pool.map(cpu_bound_task, [100_000_000, 100_000_000])
    parallel_time = time.time() - start
    print(f"Multiprocessing: {parallel_time:.2f}s")

Пример: Работа с Данными

Threading для I/O:

import threading
import queue

def producer(q):
    """Производит данные"""
    for i in range(5):
        time.sleep(0.5)  # Имитация I/O
        q.put(f"Item {i}")
    q.put(None)  # Signal to stop

def consumer(q):
    """Потребляет данные"""
    while True:
        item = q.get()
        if item is None:
            break
        print(f"Consumed: {item}")

q = queue.Queue()
t1 = threading.Thread(target=producer, args=(q,))
t2 = threading.Thread(target=consumer, args=(q,))
t1.start()
t2.start()
t1.join()
t2.join()

Multiprocessing для обработки:

import multiprocessing

def process_data(data):
    """Обработка данных"""
    return sum(data) * 2

if __name__ == '__main__':
    data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
    with multiprocessing.Pool(3) as pool:
        results = pool.map(process_data, data)
    
    print(results)  # [12, 30, 48]

Синхронизация в Threading

import threading

shared_data = {'count': 0}
lock = threading.Lock()

def increment():
    global shared_data
    for _ in range(100_000):
        with lock:  # Защищаем доступ к данным
            shared_data['count'] += 1

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

print(shared_data['count'])  # Всегда 200_000

Синхронизация в Multiprocessing

import multiprocessing

def worker(queue, result):
    """Рабочий процесс"""
    value = queue.get()
    result.put(value * 2)

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    result = multiprocessing.Queue()
    
    queue.put(5)
    
    p = multiprocessing.Process(target=worker, args=(queue, result))
    p.start()
    p.join()
    
    print(result.get())  # 10

Python 3.13+: Удаление GIL

В новых версиях Python работает над удалением GIL (Global Interpreter Lock), что позволит threading работать с истинным параллелизмом.

Таблица Выбора

ЗадачаИнструментПричина
Web scrapingThreadingI/O-bound, GIL не блокирует
Обработка больших файловMultiprocessingCPU-bound
Database queriesThreadingI/O-bound
Математические вычисленияMultiprocessingCPU-bound, обходим GIL
Async HTTP requestsasyncioI/O-bound, без GIL
Image processingMultiprocessingCPU-bound
Telegram botasyncioI/O-bound

Рекомендация

Для собеседования:

  1. Threading — для I/O (сеть, файлы, БД). GIL ограничивает CPU, но не блокирует при ожидании I/O.
  2. Multiprocessing — для CPU-интенсивных задач. Обходит GIL, но больше overhead.
  3. asyncio — лучший выбор для I/O на одном потоке (особенно web).
  4. GIL — глобальная блокировка, позволяет только одному потоку выполнять bytecode.

При выборе спроси:

  • I/O или CPU-bound?
  • Нужна ли общая память?
  • Сложность синхронизации?
  • Требования к производительности?