Python: как сделать многопоточную программу
medium

Python: как сделать многопоточную программу

Оптимизируем простейший таймер.

Когда-то давно мы делали простой таймер с напоминанием на Python. Он работал так:

  1. Мы спрашивали пользователя, о чём ему напомнить и через сколько минут.
  2. Программа на это время засыпала и ничего не делала.
  3. Как только время сна заканчивалось, программа просыпалась и выводила напоминание.

У такой схемы есть минус: мы не можем пользоваться программой и выделенными на неё ресурсами до тех пор, пока она не проснётся. Процессор по кругу гоняет пустые команды и ждёт, когда можно будет продолжить полезную работу. Чтобы процессор и программа могли во время работы таймера делать что-то ещё, используют потоки.

Что такое поток

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

Но если мы сделаем в программе два потока задач, то они будут работать параллельно и независимо друг от друга. Одному потоку не нужно будет становиться на паузу, когда в другом что-то происходит.

👉 Важно понимать, что поток — это высокоуровневое понятие из области программирования. На уровне вашего «железа» эти потоки всё ещё могут обсчитываться последовательно. Но благодаря тому, что они будут обсчитываться быстро, вам может показаться, что они работают параллельно. 

Многопоточность

Представим такую ситуацию: 

  • У вас на руке смарт-часы, которые собирают данные о вашем пульсе, УФ-излучении и движениях. На смарт-часах работает программа, которая обрабатывает эти данные.
  • Программа состоит из четырёх функций. Первая собирает данные с датчиков. Три другие обрабатывают эти данные и делают выводы. 
  • Пока первая функция не собрала нужные данные, ничего другого не происходит.
  • Как только данные введены, запускаются три оставшиеся функции. Они не зависят друг от друга и каждая считает своё.
  • Как только все три функции закончат работу, программа выдаёт нужный результат.

А теперь давайте посмотрим, как это выглядит в однопоточной и многопоточной системе. Видно, что если процессор позволяет делать несколько дел одновременно, в многопоточном режиме программа будет работать быстрее:

Python: как сделать многопоточную программу

Многопоточность на Python

За потоки в Python отвечает модуль threading, а сам поток можно создать с помощью класса Thread из этого модуля. Подключается он так:

from threading import Thread

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

  1. Подключаем нужный модуль и класс Thread.
  2. Пишем функции, которые нам нужно выполнять в потоках.
  3. Создаём новую переменную — поток, и передаём в неё название функции и её аргументы. Один поток = одна функция на входе.
  4. Делаем так столько потоков, сколько требует логика программы.
  5. Потоки сами следят за тем, закончилась в них функция или нет. Пока работает функция — работает и поток.
  6. Всё это работает параллельно и (в теории) не мешает друг другу.

Для иллюстрации запустим такой код:

import time
from threading import Thread

def sleepMe(i):
    print("Поток %i засыпает на 5 секунд.\n" % i)
    time.sleep(5)
    print("Поток %i сейчас проснулся.\n" % i)
for i in range(10):
    th = Thread(target=sleepMe, args=(i, ))
    th.start()

А вот как выглядит результат. Обратите внимание — потоки просыпаются не в той последовательности, как мы их запустили, а в той, в какой их выполнил процессор. Иногда это может помешать работе программы, но про это мы поговорим отдельно в другой статье.

Многопоточность в Python: обратите внимание — потоки просыпаются не в той последовательности, как мы их запустили, а в той, в какой их выполнил процессор

Добавляем потоки в таймер

Первое, что нам нужно сделать, — вынести код таймера-напоминания в отдельную функцию, чтобы создать с ней поток. Для этого используем команду def:

# Делаем отдельную функцию с напоминанием
def remind():
    # Спрашиваем текст напоминания, который нужно потом показать пользователю
    print("О чём вам напомнить?")
    # Ждём ответа пользователя и результат помещаем в строковую переменную text
    text = str(input())
    # Спрашиваем про время
    print("Через сколько минут?")
    # Тут будем хранить время, через которое нужно показать напоминание
    local_time = float(input())
    # Переводим минуты в секунды
    local_time = local_time * 60
    # Ждём нужное количество секунд, программа в это время ничего не делает
    time.sleep(local_time)
    # Показываем текст напоминания
    print(text)

Теперь сделаем новый поток, в который отправим выполняться нашу новую функцию. Так как аргументов у нас нет, то и аргументы передавать не будем, а напишем args=().

# Создаём новый поток
th = Thread(target=remind, args=())
# И запускаем его
th.start()

Нам осталось убедиться в том, что пока поток работает, мы можем выполнять в программе что-то ещё и наше засыпание в потоке на это не повлияет. Для этого мы через 20 секунд после запуска выведем сообщение на экран. За 20 секунд пользователь успеет ввести напоминание и время, после чего таймер уйдёт в новый поток и там уснёт на нужное количество минут. Но это не помешает работе основной программы — она тоже будет выполнять свои команды параллельно с потоком.

# Пока работает поток, выведем что-то на экран через 20 секунд после запуска
time.sleep(20)
print("Пока поток работает, мы можем сделать что-нибудь ещё.\n")

Результат:

Добавляем потоки в таймер: результат

# Подключаем модуль для работы со временем
import time

# Подключаем потоки
from threading import Thread

# Делаем отдельную функцию с напоминанием
def remind():
    # Спрашиваем текст напоминания, который нужно потом показать пользователю
    print("О чём вам напомнить?")
    # Ждём ответа пользователя и результат помещаем в строковую переменную text
    text = str(input())
    # Спрашиваем про время
    print("Через сколько минут?")
    # Тут будем хранить время, через которое нужно показать напоминание
    local_time = float(input())
    # Переводим минуты в секунды
    local_time = local_time * 60
    # Ждём нужное количество секунд, программа в это время ничего не делает
    time.sleep(local_time)
    # Показываем текст напоминания
    print(text)

# Создаём новый поток
th = Thread(target=remind, args=())
# И запускаем его
th.start()

# Пока работает поток, выведем что-то на экран через 20 секунд после запуска
time.sleep(20)
print("Пока поток работает, мы можем сделать что-нибудь ещё.\n")

Потоки — это ещё не всё

В Python кроме потоков есть ещё очереди (queue) и управление процессами (multiprocessing). Про них мы поговорим отдельно.

Текст и иллюстрации

Миша Полянин



Заглавная иллюстрация

Даня Берковский


Корректор

Ира Михеева


Вёрстка

Маша Дронова


Соцсети

Олег Вешкурцев


Да продлится многопоточность

во веки веков!

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
Моделируем игру в рулетку на Python
Моделируем игру в рулетку на Python

И строим наглядные графики.

easy
Телеграм-бот на Python
Телеграм-бот на Python

15 минут — и можете запускать своего первого бота.

medium
Делаем свой таймер на Python
Делаем свой таймер на Python

Код — проще, возможностей — больше.

easy
Поднимаем свой сайт на хостинге с нуля за 5 минут
Поднимаем свой сайт на хостинге с нуля за 5 минут

Объясняем в картинках, как это сделать

easy
Запускаем нейросеть на домашнем компьютере
Запускаем нейросеть на домашнем компьютере

Пошаговое руководство для начинающих

hard
Играем в двадцать одно на компьютере
Играем в двадцать одно на компьютере

Свой проект с блек-джеком и JavaScript

hard
Собираем свой веб-браузер из готовых элементов за 20 минут
Собираем свой веб-браузер из готовых элементов за 20 минут

Сможете писать в портфолио, что создали свой браузер :-)

hard
Ваш собственный телеграм-секретарь: делаем вместе
Ваш собственный телеграм-секретарь: делаем вместе

Пошаговая инструкция для тех, кому нужен секретарь.

easy
Что означает ошибка: TypeError: toUpperCase is not a function
Что означает ошибка: TypeError: toUpperCase is not a function

Функция есть, но она не знает, что ей делать

easy
medium