Skip to content

Thread Class - Потоковый класс

Класс Thread в Python является частью модуля threading и представляет собой поток выполнения. Потоки позволяют выполнять несколько задач параллельно, что может повысить производительность программы.

Из официальной документации:
python
Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

Описание параметров:

ПараметрОписание (англ.)Описание (рус.)
groupIf not None, thread is a daemon. If a thread is a member of a target thread’s group, the remainder of its initialization is done in the other thread.Если не None, поток является демоном. Если поток является членом группы целевого потока, остальная часть его инициализации выполняется в другом потоке.
targetCallable object invoked by run() method. Defaults to None, meaning nothing is called.Вызываемый объект, вызываемый методом run(). По умолчанию None, что означает, что ничего не вызывается.
nameThread name. By default, a unique name is constructed of the form “Thread-N” where N is a small decimal number.Имя потока. По умолчанию, уникальное имя формируется в виде “Thread-N”, где N - небольшое десятичное число.
argsArgument tuple for the target invocation. Defaults to ().__Кортеж аргументов для вызова целевого объекта. По умолчанию ().
kwargsKeyword argument dictionary for the target invocation. Defaults to {}.Словарь ключевых аргументов для вызова целевого объекта. По умолчанию {}.
daemonA boolean indicating whether this thread is a daemon thread (True) or not (False). If None (the default), the daemon status is inherited from the current thread.Булевое значение, указывающее, является ли этот поток демон-потоком (True) или нет (False). Если None (по умолчанию), статус демона наследуется от текущего потока.

Описание методов класса Thread (из документации):

МетодОписание (англ.)Описание (рус.)
start()Start thread’s activity. It must be called at most once per thread object.Запускает деятельность потока. Должен вызываться не более одного раза для каждого объекта потока.
run()Method representing the thread’s activity. You may override this method in a subclass.Метод, представляющий деятельность потока. Вы можете переопределить этот метод в подклассе.
join([timeout])Wait until thread terminates.Ожидает до тех пор, пока поток не завершится.
is_alive()Return whether the thread is alive.Возвращает состояние активности потока.
getName()Return thread's name.Возвращает имя потока.
setName(name)Set thread's name.Устанавливает имя потока.
isDaemon()Return whether the thread is a daemon thread.Вернуть True, если поток является демоном.
setDaemon(daemonic)Set whether the thread is a daemon thread.Установить, является ли поток демоном.

Реализация класса наследника Thread

Для создания собственного класса, наследуемого от Thread, необходимо импортировать модуль threading и создать класс, который будет наследовать Thread. В этом классе нужно переопределить метод run(), который будет содержать код, выполняемый в потоке.

python
from threading import Thread
import time

class CustomThread(Thread):
    def __init__(self, name, delay):
        super().__init__()
        self.name = name
        self.delay = delay
        
    def run(self):
        print(f"Thread {self.name} started")
        time.sleep(self.delay)
        print(f"Thread {self.name} finished")

thread1 = CustomThread("Thread-1", 2)
thread2 = CustomThread("Thread-2", 4)

thread1.start()
thread2.start()

В этом примере создали класс CustomThread, наследуемый от Thread. В методе run() выполняем необходимую логику, выводим сообщения о запуске и завершении потока.

Давайте разберем создание наследуемого от Thread класса в Python и работу с его методами, включая передачу параметров, timeout и join().

Создание наследуемого класса и передача параметров

Для создания класса, наследуемого от Thread, нужно определить класс и переопределить метод run(). Параметры target, name, args, kwargs, daemon можно передать в конструктор родительского класса Thread.

python
from threading import Thread
import time

class CustomThread(Thread):
    def __init__(self, target=None, name=None, args=(), kwargs=None, daemon=None):
        super().__init__(target=target, name=name, args=args, kwargs=kwargs, daemon=daemon)
        self.result = None

    def run(self):
        if self.target:
            self.result = self.target(*self.args, **self.kwargs)


def my_function(a, b, c=1):
    time.sleep(2)  # Имитация длительной операции
    return a + b + c


# Пример использования:
my_thread = CustomThread(target=my_function, args=(1, 2), kwargs={'c': 3}, name="MyThread")
my_thread.start()

# Дополнительные действия, пока поток работает в фоне (благодаря daemon=True)
print("Главный поток продолжает выполнение...")

# Если нужно дождаться завершения потока, несмотря на daemon=True:
my_thread.join()
print(f"Результат выполнения потока: {my_thread.result}")

Поток с внешней функцией против класса наследника Thread

python
from threading import Thread
import time

# Подход с внешней функцией
def external_function(name, delay):
    print(f"Thread {name} started")
    time.sleep(delay)
    print(f"Thread {name} finished")

thread = Thread(target=external_function, args=("Thread-1", 2))
thread.start()


# Подход с наследованием
class CustomThread(Thread):
    def __init__(self, name, delay):
        super().__init__()
        self.name = name
        self.delay = delay
        
    def run(self):
        print(f"Thread {self.name} started")
        time.sleep(self.delay)
        print(f"Thread {self.name} finished")

thread = CustomThread("Thread-2", 2)
thread.start()

Преимущества и недостатки класса наследника Thread против Thread класса с внешней функцией

ХарактеристикаНаследование ThreadИспользование внешней функции
Организация кодаЛучше для сложной логики, сохраняет связанный код вместеПроще для простых задач
Совместное использование данныхЛегче доступ к переменным экземпляраТребуется передача данных в качестве аргументов
Повторное использованиеБолее повторно используемый и расширяемыйМенее повторно используемый, особенно при различных потребностях в данных
СложностьНемного сложнее настроитьЛегче понять для начинающих

Сравнение работы обычного непоточного класса и класса наследника Thread

В первом примере создали обычный непоточный класс NonThreadedClass. Деятельность объектов этого класса выполняется последовательно, что означает, что следующий объект не запустится, пока предыдущий не завершится.

python
import time

class NonThreadedClass:
    def __init__(self, name):
        self.name = name

    def run(self):
        print(f"Объект {self.name} запущен")
        time.sleep(2)
        print(f"Объект {self.name} завершен")

non_thread_1 = NonThreadedClass("Object-1")
non_thread_2 = NonThreadedClass("Object-2")
non_thread_3 = NonThreadedClass("Object-3")

non_thread_1.run()
non_thread_2.run()
non_thread_3.run()

Во втором примере создали класс ThreadedClass, наследуемый от Thread. Деятельность потоков этого класса выполняется параллельно, что означает, что все потоки запускаются одновременно и выполняются независимо друг от друга.

python
import threading
import time

class ThreadedClass(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run(self):
        print(f"Поток {self.name} запущен")
        time.sleep(2)
        print(f"Поток {self.name} завершен")

thread1 = ThreadedClass("Thread-1")
thread2 = ThreadedClass("Thread-2")
thread3 = ThreadedClass("Thread-3")

thread1.start()
thread2.start()
thread3.start()

thread1.join()
thread2.join()
thread3.join()

В результате использование потоков позволяет повысить производительность программы, поскольку деятельность потоков может выполняться параллельно, а не последовательно.

Преимущества использования потоков через наследование:

  1. Лучшая организация кода
  2. Возможность хранения состояния
  3. Возможность расширения функционала
  4. Более удобное управление потоком

Упражнения

  1. Есть функция, которая выводит квадраты чисел. Перепишите её в виде класса-наследника Thread так, чтобы каждый вывод квадрата выполнялся в отдельном потоке.

    python
    def print_squares(numbers):
        for n in numbers:
            print(f"Square of {n}: {n**2}")
            time.sleep(1)
  2. Имеется код, где создаются несколько потоков с функцией, которая суммирует элементы списка. Перепишите этот код так, чтобы каждый поток был объектом класса-наследника Thread.

    python
    def sum_list(lst):
        print(f"Сумма списка {lst}: {sum(lst)}")
        
    lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    threads = []
    
    for lst in lists:
        thread = threading.Thread(target=sum_list, args=(lst,))
        threads.append(thread)
        thread.start()
    
    for thread in threads:
        thread.join()
  3. Работа со строкой. Дополнить метод run() для подсчета гласных букв.

    python
    class StringCounter(Thread):
        def __init__(self, text):
            Thread.__init__(self)
            self.text = text
  4. Работа со списком. Дополнить метод run() для нахождения среднего значения.

    python
    class ListProcessor(Thread):
        def __init__(self, numbers):
            Thread.__init__(self)
            self.numbers = numbers
  5. Дополнить класс методами для чтения файла и подсчета строк

    python
    class FileReader(Thread):
        def __init__(self, filename):
            Thread.__init__(self)
            self.filename = filename
  6. Функция, которая печатает числа от 1 до n с задержкой delay между каждым числом.

  7. Создайте класс-поток, который принимает строку и переводит ее в верхний регистр.

  8. Создайте класс-поток, который имитирует работу таймера, выводящего текущее время каждую секунду.

  9. Создайте класс-наследник Thread, который принимает строку и выводит её символы по одному в отдельном потоке. Каждый символ строки может выводиться с задержкой, чтобы имитировать работу потока.

  10. Создайте класс-наследник Thread, который принимает список чисел и выводит их квадраты в отдельном потоке. Запуск каждого потока должен происходить параллельно.

  11. Создайте класс-наследник Thread, который запускает несколько потоков для выполнения различных математических операций (например, умножение, деление и сложение) над числами.

    Допустим, у вас есть несколько чисел, и вы хотите запустить три различных потока для выполнения трех операций:

    • Один поток умножает числа.
    • Второй поток делит числа.
    • Третий поток складывает числа. Запуск этих потоков должен происходить параллельно, а результат можно выводить по завершению каждого потока.

Contacts: teffal@mail.ru