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

Какие плюсы и минусы поточности?

2.0 Middle🔥 181 комментариев
#Другое

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

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

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

Многопоточность в Python: плюсы и минусы

Многопоточность позволяет выполнять несколько задач одновременно. Разберу фундаментальные различия в Python из-за GIL.

Плюсы многопоточности

1. Параллелизм I/O операций

Пока один поток ждёт ответ от сервера, другой может выполнять вычисления. Идеально для сетевых операций.

import threading
import requests
import time

def fetch_url(url):
    response = requests.get(url)
    print(f"Скачал {len(response.content)} байт из {url}")

# Однопоточно: 6 сек (3 запроса по 2 сек каждый)
start = time.time()
for url in ["https://api.github.com", "https://api.example.com", "https://api.other.com"]:
    fetch_url(url)
print(f"Однопоточно: {time.time() - start:.1f} сек")

# Многопоточно: 2 сек (все 3 запроса параллельно)
start = time.time()
threads = []
for url in urls:
    t = threading.Thread(target=fetch_url, args=(url,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()
print(f"Многопоточно: {time.time() - start:.1f} сек")

2. Чистый код без callback hell

Последовательный стиль кода, который легче читать, чем асинхронные колбэки.

# Многопоточность: читается как синхронный код
def process_user(user_id):
    user_data = fetch_from_api(user_id)      # Блокирующий I/O
    user_profile = process_profile(user_data) # Дождёмся результата
    save_to_db(user_profile)                  # Сохраним
    return user_profile

threads = [threading.Thread(target=process_user, args=(i,)) for i in range(100)]
for t in threads:
    t.start()

3. Приватные переменные потока (thread-local)

Каждый поток может иметь свои данные без синхронизации.

import threading

local_data = threading.local()

def worker(thread_id):
    local_data.value = thread_id  # Каждый поток свой value
    print(f"Поток {thread_id}: {local_data.value}")

for i in range(3):
    threading.Thread(target=worker, args=(i,)).start()

# Выведет:
# Поток 0: 0
# Поток 1: 1
# Поток 2: 2

4. Адаптивное распределение нагрузки

Потоки могут автоматически использовать освободившиеся ресурсы.

Минусы многопоточности в Python

1. GIL (Global Interpreter Lock)

Главный враг. В CPython только один поток выполняет код Python одновременно. Это убивает параллелизм вычислений.

import threading
import time

def cpu_bound_work():
    """Вычисление, требующее CPU"""
    count = 0
    for i in range(100_000_000):
        count += i
    return count

# Однопоточно
start = time.time()
cpu_bound_work()
cpu_bound_work()
print(f"Однопоточно: {time.time() - start:.1f} сек")

# Многопоточно: медленнее из-за GIL!
start = time.time()
t1 = threading.Thread(target=cpu_bound_work)
t2 = threading.Thread(target=cpu_bound_work)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Многопоточно: {time.time() - start:.1f} сек")  # Может быть медленнее!

Результат: GIL переключает потоки каждые 5 мс, теряя производительность на контекстное переключение.

2. Сложность синхронизации

Общие переменные требуют локов. Неправильная синхронизация — дедлоки и race conditions.

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    # Плохо: race condition
    # counter += 1  # 3 операции: read, add, write
    
    # Хорошо: с локом
    with lock:
        counter += 1

threads = [threading.Thread(target=increment) for _ in range(1000)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)  # 1000 (гарантировано с локом)

3. Deadlocks

Взаимная блокировка потоков, когда каждый ждёт другого.

lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1_func():
    with lock1:
        time.sleep(0.1)
        with lock2:  # Ждёт lock2, которой занят thread2
            print("Thread 1 acquired both locks")

def thread2_func():
    with lock2:
        time.sleep(0.1)
        with lock1:  # Ждёт lock1, который занят thread1
            print("Thread 2 acquired both locks")

t1 = threading.Thread(target=thread1_func)
t2 = threading.Thread(target=thread2_func)
t1.start()
t2.start()
# DEADLOCK! Оба потока зависли

4. Отладка сложная

Race conditions проявляются редко, нелокально, неповторяемо.

# Проблема может проявиться 1 раз на 1000 запусков
def buggy_increment():
    global counter
    temp = counter      # Поток A прочитал 5
    time.sleep(0.0001) # Контекст переключился на поток B
    # Поток B вычислил counter = 6
    counter = temp + 1 # Поток A пишет 6 вместо 7

# Воспроизвести баг невозможно обычными средствами отладки

5. Overhead на создание потоков

Каждый поток требует памяти и времени на создание.

import threading
import time

# Потоки тяжелые
start = time.time()
threads = []
for i in range(10000):
    t = threading.Thread(target=lambda: None)
    t.start()
    threads.append(t)
for t in threads:
    t.join()
print(f"Создание 10k потоков: {time.time() - start:.1f} сек")  # Медленно!

Сравнение параллелизма

ПодходGILI/OCPUСложностьПамять
ThreadingБлокирует✓ Хорошо✗ ПлохоВысокаяВысокая
MultiprocessingНет GIL✓ Хорошо✓ ХорошоВысокаяВысокая
asyncioНет GIL✓ Отлично✗ НетСредняяНизкая
CoroutinesНет GIL✓ Отлично✗ НетНизкаяНизкая

Когда использовать что

# 1. I/O-bound операции (fetch URLs, DB queries)
import asyncio

async def fetch_urls():
    tasks = [fetch_url(url) for url in urls]
    await asyncio.gather(*tasks)  # Лучше всего

# 2. CPU-bound операции (вычисления)
from multiprocessing import Pool

with Pool(4) as p:
    results = p.map(cpu_heavy_function, data)  # Лучше всего

# 3. Блокирующие операции без контроля (например, библиотека)
import threading

threads = [threading.Thread(target=blocking_lib_call) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()  # Работает, но неоптимально

Лучшие практики

  1. Используй asyncio для I/O, не threading
  2. Используй multiprocessing для CPU, не threading
  3. Потоки только для legacy/блокирующих операций
  4. Минимизируй shared state, используй thread-safe структуры (Queue, Lock)
  5. Тестируй concurrency проблемы с stress-тестами

Вывод: в Python многопоточность полезна только для I/O операций. Для CPU вычислений нужен multiprocessing или asyncio.

Какие плюсы и минусы поточности? | PrepBro