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

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? Зарабатывай на нём! Любишь Python? Зарабатывай на нём! Любишь Python? Зарабатывай на нём! Любишь Python? Зарабатывай на нём!
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
Моделируем игру в рулетку на Python
Моделируем игру в рулетку на Python

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

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

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

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

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

easy
Делаем своё расширение для браузера за 10 минут
Делаем своё расширение для браузера за 10 минут

Cнова запускаем снежинки.

medium
Как сделать таймер
Создаём собственный таймер-напоминалку

Таймеров и трекеров полно, но мы сделаем такой, какой нужен именно вам. Это легко.

medium
Простой калькулятор на JavaScript
Простой калькулятор на JavaScript

Проще уже некуда

easy
Свой текстовый редактор: делаем красиво
Собственный текстовый редактор: делаем красиво

Самое простое и полезное введение в CSS.

medium
Прокачиваем собственный текстовый редактор
medium
Делаем форму обратной связи на сайте

Говорят, что если программист может написать форму обратной связи, он может написать всё.

medium
Как очень быстро и яростно добавить музыкальный трек на страницу
Как очень быстро и яростно добавить музыкальный трек на страницу

Всё для семьи

easy
Убираем спагетти-код

Два подхода к упорядочиванию хаоса.

medium
Пишем свой генератор паролей
Пишем свой генератор паролей

Готовый код с уникальным алгоритмом шифрования. Возьми и сделай.

medium
ООП: атрибуты и методы
ООП: атрибуты и методы

Два главных слова любого программиста в ООП. Знай их, люби их, говори правильно.

easy
medium