Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Поток (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 (правильно)
Преимущества потоков
- Параллелизм — несколько операций одновременно
- I/O оптимизация — один поток ждёт I/O, другой работает
- Отзывчивость UI — тяжелая работа в отдельном потоке
- Общая память — легко делиться данными между потоками
Проблемы потоков
- Race conditions — два потока изменяют одни данные одновременно
- Deadlocks — потоки ждут друг друга в круге
- Сложность отладки — многопоточный баг сложно воспроизвести
- 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.