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. Вот о ней мы и будем общаться всё ближайшее время.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ