Appearance
Threading - Потоки
Что такое поток? В каких задачах применяются?
Поток (thread) - это легковесный процесс, наименьшая единица выполнения инструкций внутри процесса. Каждый поток имеет свой собственный стек вызовов и область памяти для локальных переменных, но разделяет ресурсы процесса, такие как глобальные переменные и открытые файлы.
Представьте себе процесс как фабрику, а потоки - как рабочих на этой фабрике. Каждый рабочий может выполнять свою задачу одновременно с другими, используя общие ресурсы фабрики (память, файлы и т.д.).
Потоки применяются для решения задач, требующих параллельного выполнения, например:
- Задачи, связанные с вводом/выводом: Ожидание данных из сети, чтение/запись файлов. Пока один поток ожидает данные, другие могут выполнять другую работу.
- GUI приложения: Один поток отвечает за интерфейс, другие выполняют фоновые операции.
- Игры: Один поток обрабатывает графику, другой - логику игры, третий - звук.
- Веб-серверы: Каждый запрос клиента обрабатывается отдельным потоком.
- Научные вычисления: Распараллеливание вычислений для ускорения процесса.
Создание потока и описание его работы.
В Python существуют два основных вида потоков:
Основной поток (main thread)
- это поток, в котором начинается выполнение программы.Основной поток
создается автоматически при запуске программы и выполняет основной код программы.Дочерние потоки
- это потоки, созданные программистом для выполнения дополнительных задач.
Thread() синтаксис с описанием параметров
Для работы с потоками в Python используется модуль threading
. Основной класс - threading.Thread
.
Из официальной документации:
python
Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
Описание параметров:
Параметр | Описание (English) | Описание (Русский) |
---|---|---|
group | Reserved for future extension. Currently must be None. | Зарезервировано для будущего расширения. В настоящее время должно быть None. |
target | The callable object to be invoked by the run() method. Defaults to None, meaning nothing is called. | Вызываемый объект, который будет вызван методом run() . По умолчанию None, что означает, что ничего не вызывается. |
name | The thread name. By default, a unique name is constructed of the form “Thread-N” where N is an integer. | Имя потока. По умолчанию создается уникальное имя вида “Thread-N”, где N — целое число. |
args | The argument tuple for the target invocation. Defaults to (). | Кортеж аргументов для вызова target. По умолчанию () . |
kwargs | A dictionary of keyword arguments for the target invocation. Defaults to {}. | Словарь именованных аргументов для вызова target. По умолчанию {} . |
daemon | A 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
завершается, основной поток завершается, и поток-демон автоматически завершается, даже если он находится в бесконечном цикле.
Упражнения:
- Напишите программу, которая создает 5 потоков, каждый из которых выводит свой номер и спит случайное время от 1 до 3 секунд.
- Реализуйте программу, которая генерирует n случайных чисел и распределяет их по m спискам, используя потоки. Каждый поток заполняет один список.
- Сортировка массива: Разделите массив на части и отсортируйте каждую часть в отдельном потоке, затем объедините результаты.
- Скачивание файлов: Напишите программу, которая скачивает несколько файлов из интернета одновременно, используя потоки.
- Обработка изображений: Напишите программу, которая применяет фильтр к нескольким изображениям одновременно, используя потоки.
- Простой чат: Создайте простой чат с использованием потоков, где один поток отвечает за отправку сообщений, а другой - за их получение.
- Мониторинг папки: Напишите программу, которая мониторит папку на наличие новых файлов и обрабатывает их в отдельных потоках.
- Генератор чисел Фибоначчи: Напишите программу, которая генерирует числа Фибоначчи до заданного предела, используя потоки для вычисления каждого числа.
- Симуляция работы сервера: Создайте программу, которая симулирует работу сервера, обрабатывающего запросы от клиентов в отдельных потоках.
- Игра "Жизнь": Реализуйте игру "Жизнь" Конвея, используя потоки для расчета следующего поколения клеток.
- Многопоточный веб-скрапер: Напишите программу, которая извлекает данные с нескольких веб-страниц одновременно, используя потоки.
- Создайте программу, которая использует потоки для параллельной обработки списка чисел. Каждый поток должен возводить число в квадрат и сохранять результат в новый список.
- Реализуйте простой веб-сервер на Python с использованием потоков. Каждый входящий запрос должен обрабатываться в отдельном потоке.
- Напишите программу, которая использует демон-потоки для периодического обновления данных в базе данных. Основной поток должен обеспечивать пользовательский интерфейс.
- Создайте программу, которая использует потоки для параллельной загрузки нескольких файлов с разных URL-адресов.
- Реализуйте решение "производитель-потребитель" с использованием потоков. Производитель должен генерировать данные, а потребитель - обрабатывать их.
- Напишите программу, которая использует потоки для параллельной обработки текстовых файлов. Каждый поток должен выполнять поиск определенного слова в своем файле.
- Создайте программу, которая использует потоки для параллельной обработки изображений. Каждый поток должен применять определенный фильтр к изображению.