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

Что такое мультипоток?

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

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

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

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

Мультипоток (Multithreading)

Мультипоток — это подход к параллельному выполнению кода, при котором один процесс содержит несколько потоков (threads), работающих одновременно в общем адресном пространстве. Каждый поток — это упрощённая версия процесса, которая имеет собственный стек вызовов, но делит с другими потоками одну кучу памяти и ресурсы.

Потоки vs Процессы

import threading
import multiprocessing
import time

def work(name):
    for i in range(3):
        print(f"{name} работает: {i}")
        time.sleep(0.1)

# ПОТОКИ (threading) — общая память, лёгкие
thread1 = threading.Thread(target=work, args=("Поток-1",))
thread2 = threading.Thread(target=work, args=("Поток-2",))
thread1.start()
thread2.start()
thread1.join()  # Ожидаем завершения
thread2.join()

# ПРОЦЕССЫ (multiprocessing) — отдельная память, тяжелее
process1 = multiprocessing.Process(target=work, args=("Процесс-1",))
process2 = multiprocessing.Process(target=work, args=("Процесс-2",))
process1.start()
process2.start()
process1.join()
process2.join()

GIL (Global Interpreter Lock) в Python

Пythonовский GIL — это взаимный замок, который позволяет только одному потоку одновременно выполнять bytecode интерпретатора. Это означает, что потоки в CPython не дают настоящий параллелизм для CPU-bound задач:

import threading
import time

def cpu_bound_task():
    """Задача, требующая CPU"""
    total = 0
    for i in range(50_000_000):
        total += i
    return total

# Последовательное выполнение
start = time.time()
cpu_bound_task()
cpu_bound_task()
print(f"Последовательно: {time.time() - start:.2f}s")  # ~10 сек

# С потоками (медленнее из-за 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()
print(f"С потоками: {time.time() - start:.2f}s")  # ~10+ сек (медленнее!)

GIL НЕ влияет на I/O-bound задачи (сетевые запросы, чтение файлов), потому что во время ожидания I/O поток отпускает GIL:

import threading
import time
import requests

def fetch_url(url):
    """I/O-bound задача"""
    response = requests.get(url, timeout=5)
    return len(response.text)

urls = ["https://example.com"] * 4

# Последовательно
start = time.time()
for url in urls:
    fetch_url(url)
print(f"Последовательно: {time.time() - start:.2f}s")  # ~8 сек

# С потоками (намного быстрее!)
start = time.time()
threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"С потоками: {time.time() - start:.2f}s")  # ~2 сек

Практические примеры

Пример 1: Простой многопоточный скачиватель

import threading
from queue import Queue

def download_worker(queue, results):
    """Рабочий поток для скачивания"""
    while True:
        url = queue.get()
        if url is None:
            break
        try:
            # Имитация скачивания
            result = f"Downloaded {url}"
            results.append(result)
        finally:
            queue.task_done()

queue = Queue()
results = []

# Создаём 3 рабочих потока
workers = []
for i in range(3):
    t = threading.Thread(target=download_worker, args=(queue, results))
    t.start()
    workers.append(t)

# Добавляем задачи в очередь
for url in ["http://example.com", "http://google.com", "http://github.com"]:
    queue.put(url)

# Сигнализируем об окончании
for _ in workers:
    queue.put(None)

# Ждём завершения
for t in workers:
    t.join()

print(results)

Пример 2: Lock для защиты общих данных

import threading

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance
        self.lock = threading.Lock()  # Мьютекс
    
    def withdraw(self, amount):
        """Снять деньги (потокобезопасно)"""
        with self.lock:  # Ждём, пока другие потоки отпустят замок
            if self.balance >= amount:
                self.balance -= amount
                return True
            return False
    
    def deposit(self, amount):
        """Положить деньги (потокобезопасно)"""
        with self.lock:
            self.balance += amount

account = BankAccount(1000)

def withdraw_many():
    for _ in range(100):
        account.withdraw(1)

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

print(f"Остаток: {account.balance}")  # 500 (5 потоков × 100 снятий)

Проблемы мультипоточности

  1. Race Conditions — когда несколько потоков получают доступ к данным одновременно
  2. Deadlock — потоки ждут друг друга в бесконечном цикле
  3. Сложность отладки — поведение недетерминировано

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

  • I/O-bound операции: сетевые запросы, работа с файлами, БД
  • Асинхронные операции: фоновые задачи
  • GUI приложения: чтобы не блокировать интерфейс

Альтернативы

Для CPU-bound задач лучше использовать:

  • multiprocessing — отдельные процессы, обходит GIL
  • asyncio — асинхронное программирование в одном потоке
  • ProcessPoolExecutor — пул процессов для параллелизма

Мультипоток — мощный инструмент для I/O-bound операций, но требует осторожности с общими ресурсами и понимания ограничений GIL.