Что такое многопоточность (multithreading) в программировании?

Ответ

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

Почему используется:

  • Эффективность: Позволяет выполнять несколько задач "одновременно" (конкурентно), улучшая отзывчивость приложения и утилизацию ресурсов, особенно при наличии операций ввода-вывода (I/O-bound).
  • Разделение ресурсов: Упрощает обмен данными между частями программы, так как потоки имеют доступ к общей памяти.

Особенности в Python: Из-за Global Interpreter Lock (GIL) в CPython, только один поток может выполнять байт-код Python в любой момент времени. Это означает, что многопоточность в Python не обеспечивает истинного параллелизма для задач, интенсивно использующих процессор (CPU-bound). Однако она эффективна для задач, ожидающих завершения операций ввода-вывода (например, сетевые запросы, чтение/запись файлов), так как GIL освобождается во время этих ожиданий.

Пример использования (Python):

import threading
import time

def task_io_bound(name):
    """Имитация I/O-bound задачи."""
    print(f"Поток {name}: Начинаю I/O операцию...")
    time.sleep(2) # Имитация ожидания I/O
    print(f"Поток {name}: Завершил I/O операцию.")

def task_cpu_bound(name):
    """Имитация CPU-bound задачи."""
    print(f"Поток {name}: Начинаю CPU операцию...")
    # Простая CPU-bound задача
    _ = sum(i*i for i in range(1_000_000))
    print(f"Поток {name}: Завершил CPU операцию.")

# Пример для I/O-bound задач
print("--- Пример I/O-bound ---")
t1 = threading.Thread(target=task_io_bound, args=("A",))
t2 = threading.Thread(target=task_io_bound, args=("B",))
t1.start()
t2.start()
t1.join()
t2.join()
print("Все I/O-bound потоки завершены.n")

# Пример для CPU-bound задач (показывает ограничение GIL)
print("--- Пример CPU-bound (с GIL) ---")
t3 = threading.Thread(target=task_cpu_bound, args=("C",))
t4 = threading.Thread(target=task_cpu_bound, args=("D",))
t3.start()
t4.start()
t3.join()
t4.join()
print("Все CPU-bound потоки завершены. Обратите внимание, что они выполнялись последовательно из-за GIL.n")

Важные аспекты:

  • Накладные расходы: Потоки легче процессов, что означает меньшие накладные расходы на создание и переключение контекста.
  • Синхронизация: Требуется осторожная синхронизация доступа к общим данным (например, с помощью Lock, Semaphore, RLock), чтобы избежать состояний гонки (race conditions) и других проблем параллельного программирования.
  • Альтернативы в Python: Для истинного параллелизма CPU-bound задач в Python рекомендуется использовать модуль multiprocessing, который запускает отдельные процессы, каждый со своим интерпретатором Python и, следовательно, своим GIL.