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

Что такое поток?

1.3 Junior🔥 141 комментариев
#Асинхронность и многопоточность

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

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

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

# Поток (Thread) в программировании

Поток — это наименьшая единица выполнения кода, которая может работать независимо внутри одного процесса. Это основной механизм многопоточности.

Основные концепции

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

Процесс — это полностью независимый экземпляр программы с собственной памятью, файловыми дескрипторами и ресурсами.

Поток — это легкий вес путь выполнения внутри процесса. Все потоки одного процесса делят одну и ту же память.

Процесс A                      Процесс B
┌──────────────────────┐       ┌──────────────────────┐
│  Память (heap)       │       │  Память (heap)       │
│  Файлы               │       │  Файлы               │
│  Ресурсы             │       │  Ресурсы             │
│                      │       │                      │
│  ┌──────────────┐    │       │  ┌──────────────┐    │
│  │ Поток 1      │    │       │  │ Поток 1      │    │
│  ├──────────────┤    │       │  ├──────────────┤    │
│  │ Stack        │    │       │  │ Stack        │    │
│  │ Registers    │    │       │  │ Registers    │    │
│  └──────────────┘    │       │  └──────────────┘    │
│                      │       │                      │
│  ┌──────────────┐    │       │  ┌──────────────┐    │
│  │ Поток 2      │    │       │  │ Поток 2      │    │
│  ├──────────────┤    │       │  ├──────────────┤    │
│  │ Stack        │    │       │  │ Stack        │    │
│  │ Registers    │    │       │  │ Registers    │    │
│  └──────────────┘    │       │  └──────────────┘    │
└──────────────────────┘       └──────────────────────┘

Потоки одного процесса видят одну и ту же память (heap), поэтому они могут делиться данными. Это мощно, но опасно.

Как работает поток

Если у тебя 4-ядерный процессор и 1 поток:

Время →
┌─────────────────────────────────────────┐
│ Ядро 1: [====== Поток 1 ======]         │
│ Ядро 2: [  Пусто  ]                     │
│ Ядро 3: [  Пусто  ]                     │
│ Ядро 4: [  Пусто  ]                     │
└─────────────────────────────────────────┘
Используется 25% возможностей CPU

С 4 потоками (при правильной реализации):

Время →
┌─────────────────────────────────────────┐
│ Ядро 1: [== Поток 1 ==]                 │
│ Ядро 2: [== Поток 2 ==]                 │
│ Ядро 3: [== Поток 3 ==]                 │
│ Ядро 4: [== Поток 4 ==]                 │
└─────────────────────────────────────────┘
Используется 100% возможностей CPU

Состояния потока

Поток может быть в разных состояниях:

┌─────────────────────────────────────────┐
│ New (создан, но не запущен)             │
└────────────────┬────────────────────────┘
                 │ start()
                 ↓
┌─────────────────────────────────────────┐
│ Runnable (готов к выполнению)           │
└────────────────┬────────────────────────┘
                 │ (scheduler выбирает)
                 ↓
┌─────────────────────────────────────────┐
│ Running (выполняется на процессоре)     │
└────────────────┬────────────────────────┘
                 │ (ждёт ресурс или вызывает join/sleep)
                 ↓
┌─────────────────────────────────────────┐
│ Waiting/Blocked (ждёт что-то)           │
└────────────────┬────────────────────────┘
                 │ (ресурс освобожден)
                 ↓
┌─────────────────────────────────────────┐
│ Runnable (готов снова)                  │
└──────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ Terminated (завершен)                   │
└─────────────────────────────────────────┘

Практический пример в Python

import threading
import time

def worker(thread_id, duration):
    """Функция, которую будет выполнять поток."""
    print(f"Поток {thread_id} начал работу")
    time.sleep(duration)  # Имитация работы
    print(f"Поток {thread_id} закончил работу")

# Создание потока
thread = threading.Thread(target=worker, args=(1, 2))

# Запуск потока
thread.start()  # Поток переходит в Runnable

# Основной поток ждёт завершения thread
thread.join()  # Основной поток переходит в Waiting

print("Все потоки завершены")

Вывод:

Поток 1 начал работу
[ждём 2 секунды]
Поток 1 закончил работу
Все потоки завершены

Многопоточность в действии

import threading
import time

def download(url, duration):
    print(f"Начинаю загрузку {url}")
    time.sleep(duration)  # Имитация долгой загрузки
    print(f"Завершил загрузку {url}")

# Без потоков (последовательно) — займет 6 секунд
print("Последовательно:")
start = time.time()
download("file1.zip", 2)
download("file2.zip", 2)
download("file3.zip", 2)
print(f"Время: {time.time() - start:.1f}с\n")

# С потоками (параллельно) — займет ~2 секунды
print("С потоками:")
start = time.time()
threads = [
    threading.Thread(target=download, args=(f"file{i}.zip", 2))
    for i in range(1, 4)
]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()
print(f"Время: {time.time() - start:.1f}с")

Вывод:

Последовательно:
Начинаю загрузку file1.zip
Завершил загрузку file1.zip
Начинаю загрузку file2.zip
Завершил загрузку file2.zip
Начинаю загрузку file3.zip
Завершил загрузку file3.zip
Время: 6.0с

С потоками:
Начинаю загрузку file1.zip
Начинаю загрузку file2.zip
Начинаю загрузку file3.zip
Завершил загрузку file1.zip
Завершил загрузку file2.zip
Завершил загрузку file3.zip
Время: 2.0с

Синхронизация между потоками

Когда потоки работают с одними данными, нужна синхронизация:

import threading

class Counter:
    def __init__(self):
        self.value = 0
        self.lock = threading.Lock()  # Мьютекс для синхронизации
    
    def increment(self):
        with self.lock:  # Только один поток может быть здесь одновременно
            self.value += 1

counter = Counter()

def increment_many(counter, times):
    for _ in range(times):
        counter.increment()

threads = [
    threading.Thread(target=increment_many, args=(counter, 1000))
    for _ in range(5)
]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

print(f"Counter = {counter.value}")  # 5000 (правильно)

Преимущества потоков

  1. Параллелизм — несколько операций одновременно
  2. I/O оптимизация — один поток ждёт I/O, другой работает
  3. Отзывчивость UI — тяжелая работа в отдельном потоке
  4. Общая память — легко делиться данными между потоками

Проблемы потоков

  1. Race conditions — два потока изменяют одни данные одновременно
  2. Deadlocks — потоки ждут друг друга в круге
  3. Сложность отладки — многопоточный баг сложно воспроизвести
  4. GIL в Python — Global Interpreter Lock ограничивает параллелизм для CPU-bound операций

Python и GIL

В Python есть Global Interpreter Lock (GIL), который ограничивает параллелизм:

  • I/O-bound операции (сеть, файлы) → потоки работают хорошо
  • CPU-bound операции (вычисления) → потоки не помогут, используй multiprocessing
# Потоки НЕ помогут здесь (CPU-bound)
def compute(n):
    return sum(i*i for i in range(n))

# Лучше использовать multiprocessing или async

Резюме

Поток — это путь выполнения кода. Потоки внутри одного процесса работают параллельно и делят одну память. Это мощный инструмент для I/O операций, но требует осторожности с синхронизацией и осознания ограничений GIL в Python.