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

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

1 часть
Делаем свой таймер на Python
2 часть
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). Про них мы поговорим отдельно.

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

Миша Полянин


Редактор

Максим Ильяхов


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

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


Корректор

Ира Михеева


Вёрстка

Маша Дронова


Соцсети

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


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

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

Веб-разработка — это новый черный
А мы знаем толк в моде и поможем освоить новую специальность за полгода.
Посмотреть
Фронтенд — это новый черный
Еще по теме
prev
next
Моделируем игру в рулетку на Python
Моделируем игру в рулетку на Python

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

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

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

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

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

Генератор паролей
Превращаем генератор паролей в настоящую программу

Настало время настоящего программирования — собираем приложение из исходников.

Одной строкой: новые CSS-команды для фронтендов
Одной строкой: новые CSS-команды для фронтендов

Что можно сделать в современном вебе.

Делаем свой текстовый редактор с автосохранением

Это не так сложно, как звучит.

Что означает ошибка Exception has occurred: TypeError
Что означает ошибка Exception has occurred: TypeError

Неочевидная ошибка в типах данных Python.

Делаем собственный таймер для спорта
Делаем собственный таймер для спорта

Без рекламы и встроенных покупок.

Подсвечиваем манипуляции и пропаганду на любом сайте
Подсвечиваем манипуляции и пропаганду на любом сайте

Береги свой ум.

Цветной арканоид на JavaScript
Цветной арканоид на JavaScript

Всё как на старых приставках.

Красивые ссылки… с анимацией!

Невероятные… Фантастические… Ни капельки не бесящие.

Uncaught SyntaxError: Unexpected identifier — что это означает?
Uncaught SyntaxError: Unexpected identifier — что это означает?

Вредная ошибка, которую легко исправить.

Свой тетрис на JavaScript: прокачиваем проект
Свой тетрис на JavaScript: прокачиваем проект

Доработки, чтобы получилась настоящая игра.

medium