Многопоточность в Python [с примерами кода]

Опубликовано: 2020-11-30

Улучшение и ускорение кода — следующий шаг после приобретения базовых знаний о Python. Многопоточность — один из способов достижения этой оптимизации с помощью «Threads». Что это за нити? И чем они отличаются от процессов? Давайте узнаем.

К концу этого урока вы будете знать следующее:

  • Что такое потоки и процессы?
  • Как достигается многопоточность?
  • Каковы его ограничения?
  • Когда использовать многопоточность?

Изучайте онлайн-курсы по науке о данных от лучших университетов мира. Участвуйте в программах Executive PG, Advanced Certificate Programs или Master Programs, чтобы ускорить свою карьеру.

Оглавление

Потоки в Python

Когда мы думаем о многозадачности, мы думаем о параллельном выполнении. Многопоточность не строго параллельное выполнение. Потоки можно рассматривать как отдельные объекты потока выполнения различных частей вашей программы, работающие независимо. Таким образом, потоки не выполняются параллельно, но Python переключается с одного потока на другой так быстро, что кажется, что они параллельны.

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

И вы думаете, если потоки не работают параллельно, то как они ускоряют работу? Ответ заключается в том, что они не всегда ускоряют обработку. Многопоточность специально используется в задачах, где потоки ускоряют обработку.

Вся информация о потоке содержится в блоке управления потоком (TCB) . ТКБ состоит из следующих основных частей:

  1. Уникальный TID — идентификатор потока
  2. Указатель стека, который указывает на стек потока в процессе
  3. Счетчик программы, в котором хранится адрес инструкции, выполняемой в данный момент потоком.
  4. Состояние потока (выполняется, готово, ожидает, запущено или выполнено)

При этом процессы могут содержать несколько потоков, которые совместно используют код, данные и все файлы. И все потоки имеют свой отдельный регистр и стек, к которым у них есть доступ.

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

Переключение контекста

Теперь, как описано выше, потоки выполняются не параллельно, а последовательно. Таким образом, когда один поток T1 начинает выполнение, все остальные потоки остаются в режиме ожидания. Только после того, как T1 завершит свое выполнение, любой другой поток в очереди может начать выполняться. Python переключается с одного потока на другой так быстро, что это кажется параллельным выполнением. Это переключение мы называем «переключением контекста».

Многопоточное программирование

Рассмотрим ниже код, который использует потоки для выполнения операций с кубом и квадратом.

импорт потоков

деф кубер (сущ.) :
print( "Куб: {}" .format(n * n * n))

защита в квадрате (n) :
print( "Квадрат: {}" .format(n * n))

если __name__ == «__main__» :
# создать тему
t1 = threading.Thread(target=squarer, args=( 5 ,))
t2 = threading.Thread(target=cuber, args=( 5 ,))

# запускаем поток t1
t1.старт()
# запускаем поток t2
t2.старт()

# ждем, пока не завершится t1
t1.присоединиться()
# дождаться завершения t2
t2.присоединиться()

# оба потока завершены
печать( "Готово!" )

#Выход:
Площадь: 25
Куб: 125
Сделанный!

Теперь попробуем разобраться в коде.

Во-первых, мы импортируем модуль Threading, который отвечает за все задачи. Внутри основного мы создаем 2 потока, создавая подклассы класса Thread. Нам нужно передать цель, то есть функцию, которую необходимо выполнить в этом потоке, и аргументы, которые необходимо передать в эти функции.

Теперь, когда потоки объявлены, нам нужно их запустить. Это делается путем вызова метода start для потоков. После запуска основная программа должна дождаться завершения обработки потоков. Мы используем метод ожидания , чтобы приостановить основную программу и дождаться завершения выполнения потоков T1 и T2.

Обязательно к прочтению: Python Challenges для начинающих

Синхронизация потоков

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

Состояние гонки

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

Итак, если у вас есть два потока, которые имеют доступ к одним и тем же данным, то они оба могут получить доступ и изменить их, когда этот конкретный поток выполняется. Поэтому, когда T1 начинает выполняться и изменяет некоторые данные, T2 находится в режиме ожидания/сна. Затем T1 прекращает выполнение и переходит в спящий режим, передавая управление T2, который также имеет доступ к тем же данным. Таким образом, T2 теперь будет изменять и перезаписывать одни и те же данные, что приведет к проблемам, когда T1 снова начнется.

Цель синхронизации потоков состоит в том, чтобы гарантировать, что состояние гонки никогда не возникнет, а критический раздел кода будет получать доступ к потокам по одному синхронизированным способом.

Замки

Чтобы решить и предотвратить состояние гонки и его последствия, модуль потока предлагает класс Lock , который использует семафоры для синхронизации потоков. Семафоры — это не что иное, как бинарные флаги. Считайте их знаком «Занято» на телефонных будках, которые имеют значение «Занят» (эквивалентно 1) или «Не занят» (эквивалентно 0). Таким образом, каждый раз, когда поток сталкивается с сегментом кода с блокировкой, он должен проверить, находится ли блокировка уже в 1 состоянии. Если это так, то ему придется подождать, пока он не станет равным 0, чтобы его можно было использовать.

Класс Lock имеет два основных метода:

  1. Acquire([blocking]) : Метод Acquire принимает параметр blocking как True или False . Если блокировка для потока T1 была инициирована с блокировкой как True, он будет ждать или оставаться заблокированным до тех пор, пока критический раздел кода не будет заблокирован другим потоком T2. Как только другой поток T2 снимает блокировку, поток T1 получает блокировку и возвращает True .

С другой стороны, если блокировка для потока T1 была инициирована с параметром blocking равным False , поток T1 не будет ждать или останется заблокированным, если критический раздел уже заблокирован потоком T2. Если он увидит, что он заблокирован, он сразу вернет False и выйдет. Однако, если код не был заблокирован другим потоком, он получит блокировку и вернет True .

release() : когда для блокировки вызывается метод release, он разблокирует блокировку и вернет True. Кроме того, он проверит, ожидают ли какие-либо потоки снятия блокировки. Если они есть, то это позволит ровно одному из них получить доступ к замку.

Однако если блокировка уже разблокирована, возникает ThreadError.

Тупики

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

импорт потоков

л = многопоточность. Блокировка ()
# Перед 1-м приобретением
л.приобретать()
# Перед вторым приобретением
л.приобретать()
# Теперь получаем блокировку дважды

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

Эти условия взаимоблокировки могут проникнуть в ваш код, даже если вы этого не осознаете. Даже если вы включите вызов освобождения, ваш код может дать сбой на полпути, и освобождение никогда не будет вызвано, а блокировка останется заблокированной. Один из способов обойти это — использовать оператор with as , также называемый менеджерами контекста. Используя оператор with as , блокировка будет автоматически снята после завершения обработки или сбоя по какой-либо причине.

Читайте: Идеи и темы проекта Python

Прежде чем ты уйдешь

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

Если вам интересно узнать о науке о данных, ознакомьтесь с программой IIIT-B & upGrad Executive PG по науке о данных , которая создана для работающих профессионалов и предлагает более 10 тематических исследований и проектов, практические семинары, наставничество с отраслевыми экспертами, 1 -на-1 с отраслевыми наставниками, более 400 часов обучения и помощи в трудоустройстве в ведущих фирмах.

Что такое поток в Python?

Потоки — это объекты внутри процесса, выполнение которых может быть запланировано в Python. С точки зрения непрофессионала, поток — это вычислительный процесс, выполняемый компьютером. Это набор таких инструкций в программе, которые разработчики могут запускать независимо от других сценариев. Потоки позволяют увеличить скорость приложения за счет использования параллелизма. Это легкий процесс, который позволит задачам работать параллельно. Потоки работают независимо и максимально используют ЦП, тем самым повышая производительность ЦП.

Какая польза от многопоточности в Python?

Многопоточность — это метод многопоточности в программировании на Python, который позволяет нескольким потокам работать одновременно за счет быстрого переключения между потоками с помощью ЦП (так называемое переключение контекста). Когда мы можем разделить нашу задачу на несколько отдельных разделов, мы используем многопоточность. Например, предположим, что вам нужно выполнить сложный запрос к базе данных, чтобы получить данные и разбить этот запрос на множество отдельных запросов. В этом случае будет предпочтительнее выделить поток для каждого запроса и выполнять их все параллельно.

Что такое синхронизация потоков?

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