Skip to content

Threading - Потоки

Что такое поток? В каких задачах применяются?

Поток (thread) - это легковесный процесс, наименьшая единица выполнения инструкций внутри процесса. Каждый поток имеет свой собственный стек вызовов и область памяти для локальных переменных, но разделяет ресурсы процесса, такие как глобальные переменные и открытые файлы.

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

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

  • Задачи, связанные с вводом/выводом: Ожидание данных из сети, чтение/запись файлов. Пока один поток ожидает данные, другие могут выполнять другую работу.
  • GUI приложения: Один поток отвечает за интерфейс, другие выполняют фоновые операции.
  • Игры: Один поток обрабатывает графику, другой - логику игры, третий - звук.
  • Веб-серверы: Каждый запрос клиента обрабатывается отдельным потоком.
  • Научные вычисления: Распараллеливание вычислений для ускорения процесса.

Создание потока и описание его работы.

В Python существуют два основных вида потоков:

  • Основной поток (main thread) - это поток, в котором начинается выполнение программы. Основной поток создается автоматически при запуске программы и выполняет основной код программы.
  • Дочерние потоки - это потоки, созданные программистом для выполнения дополнительных задач.

Thread() синтаксис с описанием параметров

Для работы с потоками в Python используется модуль threading. Основной класс - threading.Thread.

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

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

ПараметрОписание (English)Описание (Русский)
groupReserved for future extension. Currently must be None.Зарезервировано для будущего расширения. В настоящее время должно быть None.
targetThe callable object to be invoked by the run() method. Defaults to None, meaning nothing is called.Вызываемый объект, который будет вызван методом run(). По умолчанию None, что означает, что ничего не вызывается.
nameThe thread name. By default, a unique name is constructed of the form “Thread-N” where N is an integer.Имя потока. По умолчанию создается уникальное имя вида “Thread-N”, где N — целое число.
argsThe argument tuple for the target invocation. Defaults to ().Кортеж аргументов для вызова target. По умолчанию ().
kwargsA dictionary of keyword arguments for the target invocation. Defaults to {}.Словарь именованных аргументов для вызова target. По умолчанию {}.
daemonA boolean value indicating whether this thread is a daemon thread True or not False. This must be set before start() is called, otherwise RuntimeError is raised. Its initial value is inherited from the creating thread; the main thread is not a daemon thread and therefore all threads created in the main thread default to daemon=False.Логическое значение, указывающее, является ли этот поток потоком-демоном True или нет False. Это должно быть установлено до вызова start(), иначе будет возбуждено исключение RuntimeError. Его начальное значение наследуется от создающего потока; главный поток не является потоком-демоном, поэтому все потоки, созданные в главном потоке, по умолчанию имеют daemon=False.

В примере создаем новый поток, который будет выполнять функцию my_function. Метод start() запускает поток.

python
from threading import Thread

def my_function():
    # Код, который будет выполняться в потоке
    print("Поток запущен!")

# Создание нового потока
thread = Thread(target=my_function) 

# Запуск потока
thread.start()

Описание работы потока:

Thread(target=..., args=...): Создает объект потока. target - функция, которая будет выполняться в потоке. args - аргументы функции (кортеж).

Когда вы вызываете thread.start(), создается новый поток и начинает выполнять функцию, указанную в параметре target. Этот поток выполняется параллельно с основным потоком программы.

Базовые методы управления потоком:

  • start(): Запускает поток.
  • join([timeout]): Ожидает завершения потока. Основной поток будет приостановлен до тех пор, пока не завершится поток, для которого вызван join(), или до истечения указанного timeout (в секундах).
  • is_alive(): Возвращает True, если поток запущен, и False в противном случае.
  • getName(): Возвращает имя потока.
  • setName(): Устанавливает имя потока.

Примеры работы программ без потоков и с потоками.

Пример без потоков:

python
import time

def task(name):
  print(f"Задача {name} началась")
  time.sleep(2)  # Имитация длительной операции
  print(f"Задача {name} завершена")

start_time = time.time()

task("1")
task("2")
task("3")

end_time = time.time()
print(f"Время выполнения: {end_time - start_time} секунд")

В первом примере задачи выполняются последовательно, общее время выполнения примерно 6 секунд.

Пример с тремя потоками:

python
from threading import Thread
import time

def task(name):
  print(f"Задача {name} началась")
  time.sleep(2)  # Имитация длительной операции
  print(f"Задача {name} завершена")

start_time = time.time()

threads = []
for i in range(1, 4):
  thread = Thread(target=task, args=(str(i),))
  threads.append(thread)
  thread.start()

for thread in threads:
  thread.join()

end_time = time.time()
print(f"Время выполнения: {end_time - start_time} секунд")

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

Пример графической визуализации работы потоков:

python
from threading import Thread
from tkinter import Tk, Button, Label
from random import randint
import time

amount = 1
place = 1

def go_thread(widget, text):
    global place
    text += '#'
    widget.config(text=text)
    if len(text) < 30:
        time.sleep(randint(0, 3))
        go_thread(widget, text)
    else:
        text += f'  {place} place'
        widget.config(text=text, bg="lightgreen")
        place += 1

def run_thread():
    global amount
    label = Label()
    label.pack(anchor="w", padx=5, pady=3)
    Thread(target=go_thread, args=(label, f'Thread {amount}: ')).start()
    amount += 1

root = Tk()
root.title('Monogenes')
root.minsize(250,50)
Button(text="Run a new thread", command=run_thread).pack()
root.mainloop()

Особенности применения timeout и join([timeout])

Метод join([timeout]) блокирует вызывающий поток до завершения потока, для которого он вызван, или до истечения указанного timeout (в секундах).

python
from threading import Thread
import time

def long_task():
    time.sleep(5)
    print("Длительная задача завершена")

thread = Thread(target=long_task)
thread.start()

try:
    thread.join(2)  # Ждем завершения потока максимум 2 секунды
    if thread.is_alive():
        print("Поток не завершился за отвеленное время")
    else:
        print("Поток завершился успешно")
except Exception as e:
    print(f"Произошла ошибка: {e}")

print("Продолжение выполнения основного потока")

В этом примере основной поток ждет завершения long_task максимум 2 секунды. Поскольку long_task выполняется 5 секунд, join() вернет управление через 2 секунды, и будет выведено сообщение "Поток не завершился за отведенное время". Основной поток продолжит выполнение, а long_task будет работать в фоне.

ВАЖНО

timeout в join() не прерывает поток. Он просто прекращает ожидание. Если нужно прервать поток, используйте более сложные механизмы, например, флаги остановки или Event.

Потоки-демоны.

Основной поток - это поток, который запускается при запуске программы.

Поток-демон - это поток, который не препятствует завершению основной программы. Когда все не-демон потоки завершаются, программа завершается, даже если потоки-демоны все еще работают. Они как бы "работают в фоновом режиме" и автоматически завершаются при выходе из программы.

Потоки-демоны обычно используются для выполнения фоновых задач, которые не являются критичными для завершения программы, например:

  • Мониторинг системы
  • Логирование
  • Обслуживание соединений

Пример работы с тремя потоками-демонами:

python
from threading import Thread
import time

def daemon_task(name):
    print(f"Демон {name}: начало")
    time.sleep(2)
    print(f"Демон {name}: конец")  # Cтрока не выполниться, если основной поток завершится

threads = []
for i in range(3):
    thread = Thread(target=daemon_task, args=(chr(65 + i),), daemon=True)
    threads.append(thread)
    thread.start()

print("Основной поток завершен")  # Демоны завершатся сразу после этой строки

В отличие от обычных потоков, основной поток не ждет завершения потоков-демонов. Если основной поток завершается, все демоны завершаются автоматически, даже если они не закончили свою работу. В примере выше, сообщение "Демон ...: конец" может не вывестись, если время выполнения основного потока меньше времени сна в демона

Пример работы с тремя бесконечными потоками-демонами:

python
from threading import Thread
import time

def daemon_task(name):
  while True:
    print(f"Демон-поток {name} работает...")
    time.sleep(1)

threads = []
for i in range(1, 4):
  thread = Thread(target=daemon_task, args=(str(i),), daemon=True)
  threads.append(thread)
  thread.start()

time.sleep(3)  # Основной поток работает 3 секунды
print("Основной поток завершается")

В этом примере мы создаем три потока-демона. Обратите внимание на параметр daemon=True при создании потока. Когда основной поток завершается (после time.sleep(3)), программа завершается, и все потоки-демоны автоматически завершаются, даже если они находятся в середине выполнения.

Если бы в предыдущем примере мы не установили daemon=True, то программа продолжала бы работать, пока все три потока не завершатся (а они работают бесконечно).

С daemon=True программа завершается после 3 секунд работы основного потока, не дожидаясь завершения потоков-демонов. Таким образом, потоки-демоны, нужны для фоновых задач, которые не должны блокировать завершение основной программы.

Пример использования потока-"Димон" 😉

python
from threading import Thread
import time
import random

def monitor_temperature():
    while True:
        temperature = random.randint(20, 30)
        print(f"Температура: {temperature}°C")
        if temperature > 28:
            print("Внимание! Высокая температура!")
        time.sleep(1)

def main_task():
    print("Основная задача запущена")
    time.sleep(5)
    print("Основная задача завершена")

# Создаем поток-демон для мониторинга температуры
monitor_thread = Thread(target=monitor_temperature, daemon=True)
monitor_thread.start()

# Выполняем основную задачу
main_task()

В этом примере поток-демон monitor_temperature мониторит температуру в фоновом режиме. Основной поток выполняет свою задачу main_task. Когда main_task завершается, основной поток завершается, и поток-демон автоматически завершается, даже если он находится в бесконечном цикле.

Упражнения:

  1. Напишите программу, которая создает 5 потоков, каждый из которых выводит свой номер и спит случайное время от 1 до 3 секунд.
  2. Реализуйте программу, которая генерирует n случайных чисел и распределяет их по m спискам, используя потоки. Каждый поток заполняет один список.
  3. Сортировка массива: Разделите массив на части и отсортируйте каждую часть в отдельном потоке, затем объедините результаты.
  4. Скачивание файлов: Напишите программу, которая скачивает несколько файлов из интернета одновременно, используя потоки.
  5. Обработка изображений: Напишите программу, которая применяет фильтр к нескольким изображениям одновременно, используя потоки.
  6. Простой чат: Создайте простой чат с использованием потоков, где один поток отвечает за отправку сообщений, а другой - за их получение.
  7. Мониторинг папки: Напишите программу, которая мониторит папку на наличие новых файлов и обрабатывает их в отдельных потоках.
  8. Генератор чисел Фибоначчи: Напишите программу, которая генерирует числа Фибоначчи до заданного предела, используя потоки для вычисления каждого числа.
  9. Симуляция работы сервера: Создайте программу, которая симулирует работу сервера, обрабатывающего запросы от клиентов в отдельных потоках.
  10. Игра "Жизнь": Реализуйте игру "Жизнь" Конвея, используя потоки для расчета следующего поколения клеток.
  11. Многопоточный веб-скрапер: Напишите программу, которая извлекает данные с нескольких веб-страниц одновременно, используя потоки.
  12. Создайте программу, которая использует потоки для параллельной обработки списка чисел. Каждый поток должен возводить число в квадрат и сохранять результат в новый список.
  13. Реализуйте простой веб-сервер на Python с использованием потоков. Каждый входящий запрос должен обрабатываться в отдельном потоке.
  14. Напишите программу, которая использует демон-потоки для периодического обновления данных в базе данных. Основной поток должен обеспечивать пользовательский интерфейс.
  15. Создайте программу, которая использует потоки для параллельной загрузки нескольких файлов с разных URL-адресов.
  16. Реализуйте решение "производитель-потребитель" с использованием потоков. Производитель должен генерировать данные, а потребитель - обрабатывать их.
  17. Напишите программу, которая использует потоки для параллельной обработки текстовых файлов. Каждый поток должен выполнять поиск определенного слова в своем файле.
  18. Создайте программу, которая использует потоки для параллельной обработки изображений. Каждый поток должен применять определенный фильтр к изображению.

Contacts: teffal@mail.ru