Модуль threading

Модуль 1: Python Core
14 уровень , 1 лекция
Открыта

2.1 Модуль threading

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

Основные понятия многопоточности в Python:

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

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

GIL — это механизм в интерпретаторе Python, который предотвращает выполнение нескольких потоков Python одновременно. GIL обеспечивает безопасность выполнения Python кода, но ограничивает производительность многопоточных программ на многоядерных процессорах.

Важно! Учтите, что из-за Global Interpreter Lock (GIL) многопоточность в Python может не обеспечить значительного увеличения производительности для вычислительно интенсивных задач, поскольку GIL предотвращает одновременное выполнение нескольких потоков Python на многоядерных процессорах.

Модуль threading

Модуль threading в Python предоставляет высокоуровневый интерфейс для работы с потоками. Он позволяет создавать и управлять потоками, синхронизировать их и организовывать взаимодействие между ними. Давайте рассмотрим ключевые компоненты и функции этого модуля более подробно.

Основные компоненты модуля threading

Сущности для работы с потоками:

  • Thread — основной класс для создания и управления потоками.
  • Timer — таймер для выполнения функции по истечении заданного времени.
  • ThreadLocal — позволяет создавать данные, локальные для потока.

Механизм синхронизации потоков:

  • Lock — примитив синхронизации для предотвращения одновременного доступа к общим ресурсам.
  • Condition — условная переменная для более сложной синхронизации потоков.
  • Event — примитив для уведомления между потоками.
  • Semaphore — примитив для ограничения количества потоков, которые могут одновременно выполнять определённый участок.
  • Barrier — синхронизирует заданное количество потоков, блокируя их до тех пор, пока все они не достигнут барьера.

Ниже я расскажу про 3 класса для работы с потоками, механизм же синхронизации потоков вам в ближайшее время не понадобится.

2.2 Класс Thread

Класс Thread — основной класс для создания и управления потоками. У него есть 4 основных метода:

  • start(): Запускает выполнение потока.
  • join(): Текущий поток приостанавливается и ждёт завершения запущенного потока.
  • is_alive(): Возвращает True, если поток выполняется.
  • run(): Метод, содержащий код, который будет выполняться в потоке. Переопределяется при наследовании от класса Thread.

Всё значительно проще, чем кажется — пример использования класса Thread.

Запуск простого потока


import threading

def worker():
    print("Worker thread is running")
            
# Создание нового потока
t = threading.Thread(target=worker) # создали новый объект потока
t.start() #Запустили поток
t.join() # Ожидаем завершения потока
print("Main thread is finished")
        

После вызова метода start, функция worker начнёт своё исполнение. Или, если точнее, её поток будет добавлен в список активных потоков.

Использование аргументов


import threading

def worker(number, text):
    print(f"Worker {number}: {text}")
            
# Создание нового потока с аргументами
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()
        

Для передачи параметров в новый поток нужно просто указать их в виде кортежа и присвоить параметру args. При вызове функции, которая была указана в target, параметры будут переданы автоматически.

Переопределение метода run


import threading

class MyThread(threading.Thread):
    def run(self):
        print("Custom thread is running")
            
# Создание и запуск потока
t = MyThread()
t.start()
t.join()
        

Есть два способа указать функцию, с которой нужно начать выполнение нового потока — можно передать её через параметр target при создании объекта Thread, или же унаследоваться от класса Thread и переопределить функцию run. Оба способа считаются легальными и используются постоянно.

2.3 Класс Timer

Класс Timer в модуле threading предназначен для запуска функции через заданный промежуток времени. Этот класс полезен для выполнения отложенных задач в многопоточной среде.

Таймер создаётся и инициализируется с помощью функции, которую необходимо вызвать, и времени задержки в секундах.

  • Метод start() запускает таймер, который отсчитывает заданный промежуток времени, а затем вызывает указанную функцию.
  • Метод cancel() позволяет остановить таймер, если он ещё не сработал. Это полезно для предотвращения выполнения функции, если таймер больше не нужен.

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

Запуск функции с задержкой

В этом примере функция hello будет вызвана через 5 секунд после запуска таймера.


import threading

def hello():
    print("Hello, world!")
            
# Создание таймера, который вызовет функцию hello через 5 секунд
t = threading.Timer(5.0, hello)
t.start()  # Запуск таймера
        

Остановка таймера до выполнения

Здесь таймер будет остановлен до того, как функция hello успеет выполниться, и, соответственно, ничего не будет напечатано.


import threading

def hello():
    print("Hello, world!")
            
# Создание таймера
t = threading.Timer(5.0, hello)
t.start()  # Запуск таймера
            
# Остановка таймера до выполнения
t.cancel()
        

Таймер с аргументами

В этом примере таймер вызовет функцию greet через 3 секунды и передаст ей аргумент "Alice".


import threading

def greet(name):
    print(f"Hello, {name}!")
            
# Создание таймера с аргументами
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
        

Класс Timer удобен для планирования выполнения задач через определённое время. В то же время таймеры не гарантируют абсолютно точное время выполнения, так как это зависит от загруженности системы и работы планировщика потоков.

2.4 Класс ThreadLocal

Класс ThreadLocal предназначен для создания потоков, у которых есть свои собственные локальные данные. Это полезно в многопоточных приложениях, когда каждый поток должен иметь свою версию данных, чтобы избежать конфликтов и проблем синхронизации.

Каждый поток, использующий ThreadLocal, будет иметь свои собственные независимые копии данных. Данные, сохранённые в объекте ThreadLocal, уникальны для каждого потока и не разделяются с другими потоками. Это удобно для хранения данных, которые используются только в контексте одного потока, таких как текущий пользователь в веб-приложении или текущее соединение с базой данных.

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

Основное использование

В этом примере каждый поток присваивает своё имя локальной переменной value и выводит его. Значение value уникально для каждого потока.


import threading

# Создание объекта ThreadLocal
local_data = threading.local()
            
def process_data():
    # Присвоение значения локальной переменной потока
    local_data.value = threading.current_thread().name
    # Доступ к локальной переменной потока
    print(f'Value in {threading.current_thread().name}: {local_data.value}')

threads = []
for i in range(5):
    t = threading.Thread(target=process_data)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Хранение данных пользователя в веб-приложении

В этом примере каждый поток обрабатывает запрос для своего пользователя. Значение user_data.user уникально для каждого потока.


import threading
# Создание объекта ThreadLocal
user_data = threading.local()
def process_request(user):
    # Присвоение значения локальной переменной потока
    user_data.user = user
    handle_request()

def handle_request():
    # Доступ к локальной переменной потока
    print(f'Handling request for user: {user_data.user}')

threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
    t = threading.Thread(target=process_request, args=(user,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Это были 3 самых полезных класса библиотеки threading. Скорее всего вы будете использовать их в своей работе, а вот остальные классы — навряд ли. Сейчас все переходят на асинхронные функции и библиотеку asyncio. Вот о ней мы и будем общаться всё ближайшее время.

2
Задача
Модуль 1: Python Core, 14 уровень, 1 лекция
Недоступна
Использование класса Timer
Использование класса Timer
2
Задача
Модуль 1: Python Core, 14 уровень, 1 лекция
Недоступна
Использование ThreadLocal
Использование ThreadLocal
Комментарии (3)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Slevin Уровень 64
12 июля 2025
Лекция хорошая (хоть для углублённого понимания процессов и пришлось задрачивать чатГПТ на протяжении часа, уточняя нюансы). Валидатор принял все без проблем. Ради интереса заглянул в решения задач, для первой, весьма простой задачи в несколько строк кода - написано перегруженное решение, через создание своего класса Timer(!) который затем вызывает внутри себя класс Timer из threading(!!) и передает ему значение задержки... ЗАЧЕМ? Просто ЗАЧЕМ ЭТО? Это нейросеть писала решение?
Екатерина Уровень 80
23 сентября 2024
Модуль 1: Python Core 14 уровень, 1 лекция. Вторая задача. Когда с нуля пишешь свой код, со своими переменными, со своими значениями - валидатор не принимает решение. И в какой-то момент настолько теряется мотивация, что просто копируешь код из лекции, и его принимает. Пропадает желание писать самостоятельные решения, экспериментировать, пропадает любопытство.
SWK Уровень 26
6 мая 2025
Авторы этой позорной недоделки, продаваемой под видом учебного курса, определённо - садисты. Причём, обидчивые садисты. Банят, когда указываешь им на их косяки. Т.е., им можно отбивать "желание писать самостоятельные решения" у пользователей. А публиковать выводы, которые покупатели делают на основании их безобразного поведения - нельзя.