Модуль 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Класс

Пример

import threading

class MyThread(threading.Thread):
    def run(self):
        print("Поток начинает выполнение")
        # Напишите здесь код, который будет выполняться потоком
        print("Выполнение потока завершено")

# Создайте экземпляр потока
thread = MyThread()
# Запустите поток
thread.start()
# Дождитесь завершения потока
thread.join()
print("Главный поток завершён")

Способ 2: Использованиеthreading.Threadконструктор.

Пример

import threading

def my_function():
    print("Поток начинает выполнение")
    # Напишите здесь код, который будет выполняться потоком
    print("Выполнение потока завершено")

# Создайте экземпляр потока
thread = threading.Thread(target=my_function)
# Запустите поток
thread.start()
# Дождитесь завершения потока
thread.join()
print("Главный поток завершён")

2. Синхронизация потоков

В многопоточном программировании несколько потоков могут одновременно обращаться к общим ресурсам, что может привести к проблемам с несогласованностью данных. Чтобы избежать этого, механизмы синхронизации потоков, такие как блокировки (Lock)。

Пример

import threading

# Создать объект блокировки
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 threading
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()Сбросить событие в falseevent.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. Базовое создание потоков

Пример

import threading

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. Использование блокировок для защиты общих ресурсов

Пример

lock = threading.Lock()
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. Синхронизация событий

Пример

event = threading.Event()

def waiter():
    print("Ожидание события...")
    event.wait()
    print("Событие сработало!")

t = threading.Thread(target=waiter)
t.start()

# Основной поток инициирует событие
threading.Event().wait(2.0)  # Имитация задержки
event.set()
t.join()

4. Модель производителя-потребителя (Condition)

Пример

import random
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()

Примечания

  1. Глобальная блокировка интерпретатора (GIL): GIL в Python позволяет только одному потоку одновременно выполнять байт-код Python. Поэтому в задачах с интенсивной нагрузкой на ЦП многопоточность может не обеспечить улучшения производительности. Для задач с интенсивным вводом-выводом многопоточность всё ещё полезна.

  2. Потокобезопасность: В многопоточной среде обеспечьте потокобезопасный доступ к общим ресурсам, избегая гонок данных и взаимоблокировок.

  3. Количество потоков: Создание слишком большого числа потоков может исчерпать системные ресурсы и повлиять на производительность программы. Разумно контролируйте количество потоков или используйте пул потоков (ThreadPoolExecutor) для управления потоками.


Викторина

Тест на знание модуля threading

Вопрос 1
1. Глобальная блокировка интерпретатора Python (GIL) в основном влияет на производительность какого типа многопоточных задач?
Вопрос 2
2. После создания объекта потока, какой метод следует вызвать, чтобы запустить выполнение потока?
Вопрос 3
3. Когда несколько потоков должны изменять одну и ту же глобальную переменную, что следует использовать, чтобы избежать повреждения данных?
Вопрос 4
4. С какой целью главный поток использует thread.join()?
Дополнительные расширения