Модуль threading Python
Pythonthreadingмодуль — одна из стандартных библиотек, используемых для реализации многопоточного программирования. Многопоточность позволяет программе выполнять несколько задач одновременно, тем самым повышая эффективность и отзывчивость программы.
threadingМодуль предоставляет инструменты для создания потоков и управления ими, позволяя разработчикам легко писать конкурентные программы.
Что такое поток?
Можно представить потоки как сотрудников в офисе:
-
Однопоточная программа похожа на ситуацию, когда есть только один сотрудник, который должен последовательно выполнить все задачи, такие как печать документов, ответы на электронные письма и приготовление кофе.
-
Многопоточная программа похожа на ситуацию, когда есть несколько сотрудников, которые могутодновременновыполнять разные задачи, что значительно повышает эффективность работы.
В информатике:
- Процесс: запущенная программа с независимым пространством памяти (например, браузер и музыкальный проигрыватель, открытые одновременно, — это два процесса).
- Поток: независимый поток выполнения внутри процесса и базовая единица планирования ЦП. Все потоки в одном процессесовместно используют пространство памяти процесса(например, глобальные переменные).
Зачем использовать многопоточность?
В однопоточной программе задачи выполняются одна за другой. Если задаче нужно ждать (например, ожидание ответа сети или чтения файла), вся программа блокируется до завершения задачи. Многопоточность позволяет программе продолжать выполнять другие задачи, пока она ждёт одну задачу, тем самым повышая общую производительность программы.
Потоки Python и глобальная блокировка интерпретатора (GIL)
В Python есть механизм, называемый глобальной блокировкой интерпретатора (GIL), который гарантирует, что в любой момент времени только один поток может выполнять байт-код Python.
Что это значит?Для задач, интенсивно использующих CPU (например, научные вычисления, обработка изображений), из-за GIL многопоточность обычно не может задействовать несколько ядер для ускорения вычислений и может даже замедлиться из-за накладных расходов на переключение потоков.
Итак, где многопоточность Python полезна?Для задач, интенсивно использующих I/O (например, сетевые запросы, чтение/запись файлов, ожидание пользовательского ввода), потоки освобождают GIL во время ожидания завершения операций ввода-вывода, позволяя выполняться другим потокам. Это может значительно повысить общую отзывчивость и эффективность программы, потому что пока вы ждете ответа веб-страницы, программа может обрабатывать другую задачу.
Как использовать модуль threading?
Чтобы использоватьthreadingмодуль, первым делом его нужно импортировать:
import threading import time # 用于模拟耗时操作
Самый простой способ создать поток — использоватьthreading.Threadкласс.
Объяснение синтаксиса:
thread_obj = threading.Thread(target=函数名, args=(参数元组,))
- target: Указывает функцию, которая будет выполнена после запуска потока.
- args: Аргумент, передаваемый в функцию target, должен быть кортежем. Если аргумент только один, его нужно записать в
(parameter,)виде `(arg,)`.
1. Создание потоков
В Python можно, унаследовавthreading.Threadкласс или напрямую используяthreading.Threadконструктор, создать поток.
Метод 1: Наследованиеthreading.ThreadКласс
Пример
class MyThread(threading.Thread):
def run(self):
print("Поток начинает выполнение")
# Напишите здесь код, который будет выполняться потоком
print("Выполнение потока завершено")
# Создайте экземпляр потока
thread = MyThread()
# Запустите поток
thread.start()
# Дождитесь завершения потока
thread.join()
print("Главный поток завершён")
Способ 2: Использованиеthreading.Threadконструктор.
Пример
def my_function():
print("Поток начинает выполнение")
# Напишите здесь код, который будет выполняться потоком
print("Выполнение потока завершено")
# Создайте экземпляр потока
thread = threading.Thread(target=my_function)
# Запустите поток
thread.start()
# Дождитесь завершения потока
thread.join()
print("Главный поток завершён")
2. Синхронизация потоков
В многопоточном программировании несколько потоков могут одновременно обращаться к общим ресурсам, что может привести к проблемам с несогласованностью данных. Чтобы избежать этого, механизмы синхронизации потоков, такие как блокировки (Lock)。
Пример
# Создать объект блокировки
lock = threading.Lock()
def my_function():
with lock:
print("Поток начинает выполнение")
# Напишите здесь код, который будет выполняться потоком
print("Выполнение потока завершено")
# Создайте экземпляр потока
thread1 = threading.Thread(target=my_function)
thread2 = threading.Thread(target=my_function)
# Запустите поток
thread1.start()
thread2.start()
# Дождитесь завершения потока
thread1.join()
thread2.join()
print("Главный поток завершён")
3. Взаимодействие между потоками
Межпоточное взаимодействие может быть реализовано через очередь (Queue).Queueявляется потокобезопасной и может безопасно передавать данные между несколькими потоками.
Пример
import queue
def worker(q):
while not q.empty():
item = q.get()
print(f"Обработка элемента: {item}")
q.task_done()
# Создайте очередь и заполните её данными
q = queue.Queue()
for i in range(10):
q.put(i)
# Создайте экземпляры потоков
thread1 = threading.Thread(target=worker, args=(q,))
thread2 = threading.Thread(target=worker, args=(q,))
# Запустите потоки
thread1.start()
thread2.start()
# Дождитесь обработки всех элементов в очереди
q.join()
print("Все элементы обработаны")
Общие классы, методы и атрибуты
1. Основные классы
| Класс/Метод/Атрибут | Описание | Пример |
|---|---|---|
threading.Thread | Класс Thread, используемый для создания потоков и управления ими | t = Thread(target=func, args=(1,)) |
threading.Lock | Мьютекс-блокировка (примитивная блокировка) | lock = Lock() |
threading.RLock | Повторно входимая блокировка (может быть получена несколько раз одним и тем же потоком) | rlock = RLock() |
threading.Event | Объект Event, используемый для синхронизации потоков | event = Event() |
threading.Condition | Условная переменная, используемая для сложной координации потоков | cond = Condition() |
threading.Semaphore | Семафор, управляет количеством одновременных потоков | sem = Semaphore(3) |
threading.BoundedSemaphore | Ограниченный семафор (не позволяет значению превысить начальное значение) | b_sem = BoundedSemaphore(2) |
threading.Timer | Поток таймера, отложенное выполнение | timer = Timer(5.0, func) |
threading.local | Локальные для потока данные (хранятся независимо для каждого потока) | local_data = threading.local() |
2. Общие методы/атрибуты объектов Thread
| Метод/атрибут | Описание | Пример |
|---|---|---|
start() | Запустить поток | t.start() |
run() | Метод, выполняемый потоком (можно переопределить) | Переопределите этот метод при определении пользовательского класса |
join(timeout=None) | Блокирует текущий поток до завершения целевого потока | t.join() |
is_alive() | Проверяет, выполняется ли поток | if t.is_alive(): |
name | Имя потока (можно изменить) | t.name = "Worker-1" |
daemon | Флаг потока-демона (автоматически завершается при выходе главного потока) | t.daemon = True |
ident | Идентификатор потока (`None` до запуска потока)None) | print(t.ident) |
3. Общие методы Lock/RLock
| Метод | Описание | Пример |
|---|---|---|
acquire(blocking=True, timeout=-1) | Захватить блокировку (блокирующим или неблокирующим способом) | lock.acquire() |
release() | Освободить блокировку | lock.release() |
locked() | Проверить, удерживается ли блокировка | if not lock.locked(): |
4. Общие методы Event
| Метод | Описание | Пример |
|---|---|---|
set() | Установить событие в true и разбудить все ожидающие потоки | event.set() |
clear() | Сбросить событие в false | event.clear() |
wait(timeout=None) | Блокироваться, пока событие не станет true или не истечёт тайм-аут | event.wait(2.0) |
is_set() | Проверить состояние события | if event.is_set(): |
5. Общие методы Condition
| Метод | Описание | Пример |
|---|---|---|
wait(timeout=None) | Освободить блокировку и блокироваться, пока не поступит уведомление или не истечёт тайм-аут | cond.wait() |
notify(n=1) | Разбудить не болееnожидающих потоков | cond.notify(2) |
notify_all() | Разбудить все ожидающие потоки | cond.notify_all() |
6. Функции/атрибуты уровня модуля
| Функция/атрибут | Описание | Пример |
|---|---|---|
threading.active_count() | Возвращает количество активных в данный момент потоков | print(threading.active_count()) |
threading.current_thread() | Возвращает объект текущего потока | print(threading.current_thread().name) |
threading.enumerate() | Возвращает список всех активных потоков | for t in threading.enumerate(): |
threading.main_thread() | Возвращает объект главного потока | if threading.current_thread() is threading.main_thread(): |
threading.get_ident() | Возвращает идентификатор текущего потока (Python 3.3+) | print(threading.get_ident()) |
Пример
1. Базовое создание потоков
Пример
def worker(num):
print(f"Воркер {num} запущен")
threads = []
for i in range(3):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
2. Использование блокировок для защиты общих ресурсов
Пример
count = 0
def increment():
global count
with lock: # Автоматически захватывает и освобождает блокировку
count += 1
threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(count) # Вывод: 10
3. Синхронизация событий
Пример
def waiter():
print("Ожидание события...")
event.wait()
print("Событие сработало!")
t = threading.Thread(target=waiter)
t.start()
# Основной поток инициирует событие
threading.Event().wait(2.0) # Имитация задержки
event.set()
t.join()
4. Модель производителя-потребителя (Condition)
Пример
from threading import Condition
queue = []
cond = Condition()
MAX_ITEMS = 5
def producer():
for _ in range(10):
with cond:
while len(queue) >= MAX_ITEMS:
cond.wait()
item = random.randint(1, 100)
queue.append(item)
print(f"Produced {item}")
cond.notify()
def consumer():
for _ in range(10):
with cond:
while not queue:
cond.wait()
item = queue.pop(0)
print(f"Consumed {item}")
cond.notify()
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()
Примечания
Глобальная блокировка интерпретатора (GIL): GIL в Python позволяет только одному потоку одновременно выполнять байт-код Python. Поэтому в задачах с интенсивной нагрузкой на ЦП многопоточность может не обеспечить улучшения производительности. Для задач с интенсивным вводом-выводом многопоточность всё ещё полезна.
Потокобезопасность: В многопоточной среде обеспечьте потокобезопасный доступ к общим ресурсам, избегая гонок данных и взаимоблокировок.
Количество потоков: Создание слишком большого числа потоков может исчерпать системные ресурсы и повлиять на производительность программы. Разумно контролируйте количество потоков или используйте пул потоков (
ThreadPoolExecutor) для управления потоками.