Потоки Python: введение
Опубликовано: 2022-10-25В этом руководстве вы узнаете, как использовать встроенный в Python модуль многопоточности для изучения возможностей многопоточности в Python.
Начав с основ процессов и потоков, вы узнаете, как работает многопоточность в Python, а также поймете концепции параллелизма и параллелизма. Затем вы узнаете, как запускать и выполнять один или несколько потоков в Python с помощью встроенного модуля threading
.
Давайте начнем.
Процессы и потоки: в чем разница?
Что такое процесс?
Процесс — это любой экземпляр программы, который необходимо запустить.
Это может быть что угодно — от скрипта Python или веб-браузера, такого как Chrome, до приложения для видеоконференций. Если вы запустите Диспетчер задач на своем компьютере и перейдете в «Производительность» -> « ЦП », вы сможете увидеть процессы и потоки, которые в настоящее время выполняются на ядрах вашего ЦП.

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

В ЦП с N ядрами N процессов могут выполняться параллельно в одно и то же время. Однако два потока одного и того же процесса никогда не могут выполняться параллельно, но могут выполняться одновременно. Мы рассмотрим концепцию параллелизма и параллелизма в следующем разделе.
Основываясь на том, что мы уже узнали, давайте суммируем различия между процессом и потоком.
Особенность | Процесс | Нить |
Память | Выделенная память | Общая память |
Режим исполнения | Параллельно, параллельно | Параллельно; но не параллельно |
Исполнением занимается | Операционная система | Интерпретатор CPython |
Многопоточность в Python
В Python глобальная блокировка интерпретатора (GIL) гарантирует, что только один поток может получить блокировку и запуститься в любой момент времени. Все потоки должны получить эту блокировку для запуска. Это гарантирует, что в любой момент времени может выполняться только один поток, и позволяет избежать одновременной многопоточности.
Например, рассмотрим два потока t1
и t2
одного и того же процесса. Поскольку потоки совместно используют одни и те же данные, когда t1
считывает конкретное значение k
, t2
может изменить то же самое значение k
. Это может привести к взаимоблокировкам и нежелательным результатам. Но только один из потоков может получить блокировку и работать в любой момент. Поэтому GIL также обеспечивает потокобезопасность .
Так как же добиться многопоточности в Python? Чтобы понять это, давайте обсудим концепции параллелизма и параллелизма.
Параллелизм против параллелизма: обзор
Рассмотрим ЦП с более чем одним ядром. На приведенном ниже рисунке ЦП имеет четыре ядра. Это означает, что у нас может быть четыре разных операции, выполняемых параллельно в любой момент времени.
Если процессов четыре, то каждый из процессов может работать независимо и одновременно на каждом из четырех ядер. Предположим, что каждый процесс имеет два потока.

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

Например, потоки, связанные с вводом-выводом, часто ожидают операций ввода-вывода: чтения пользовательского ввода, чтения базы данных и операций с файлами. В течение этого времени ожидания он может снять блокировку, чтобы другой поток мог работать. Время ожидания также может быть простой операцией, такой как сон в течение n
секунд.
Вкратце: во время операций ожидания поток снимает блокировку, позволяя ядру процессора переключиться на другой поток. Более ранний поток возобновляет выполнение после завершения периода ожидания. Этот процесс, при котором ядро процессора одновременно переключается между потоками, облегчает многопоточность.
Если вы хотите реализовать параллелизм на уровне процессов в своем приложении, рассмотрите возможность использования многопроцессорности.
Модуль Python Threading: первые шаги
Python поставляется с модулем threading
, который можно импортировать в сценарий Python.
import threading
Чтобы создать объект потока в Python, вы можете использовать конструктор Thread
: threading.Thread(...)
. Это общий синтаксис, которого достаточно для большинства реализаций потоков:
threading.Thread(target=...,args=...)
Здесь,
-
target
— аргумент ключевого слова, обозначающий вызываемый объект Python. -
args
— это кортеж аргументов, которые принимает цель.
Вам понадобится Python 3.x для запуска примеров кода в этом руководстве. Загрузите код и следуйте инструкциям.
Как определить и запустить потоки в Python
Давайте определим поток, который запускает целевую функцию.
Целевая функция — some_func
.
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() print(threading.active_count())
Давайте разберем, что делает приведенный выше фрагмент кода:

- Он импортирует модули
threading
иtime
. - Функция
some_func
имеет описательные операторыprint()
и включает операцию приостановки на две секунды:time.sleep(n)
заставляет функцию приостанавливаться наn
секунд. - Затем мы определяем поток
thread_1
с целью какsome_func
.threading.Thread(target=...)
создает объект потока. - Примечание . Укажите имя функции, а не вызов функции; используйте
some_func
а неsome_func()
. - Создание объекта потока не запускает поток; вызывает метод
start()
для объекта потока. - Чтобы получить количество активных потоков, мы используем
active_count()
.
Сценарий Python выполняется в основном потоке, и мы создаем еще один поток ( thread1
) для запуска функции some_func
, поэтому количество активных потоков равно двум, как видно из вывода:
# Output Running some_func... 2 Finished running some_func.
Если мы внимательно посмотрим на вывод, мы увидим, что при запуске thread1
первый оператор печати. Но во время спящего режима процессор переключается на основной поток и выводит количество активных потоков, не thread1
завершения выполнения потока 1.

Ожидание завершения выполнения потоков
Если вы хотите, чтобы thread1
завершил выполнение, вы можете вызвать для него метод join()
после запуска потока. Это приведет к ожиданию завершения выполнения thread1
без переключения на основной поток.
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() thread1.join() print(threading.active_count())
Теперь thread1
завершил выполнение до того, как мы распечатаем количество активных потоков. Таким образом, работает только основной поток, что означает, что количество активных потоков равно единице.
# Output Running some_func... Finished running some_func. 1
Как запустить несколько потоков в Python
Далее создадим два потока для запуска двух разных функций.
Здесь count_down
— это функция, которая принимает число в качестве аргумента и ведет обратный отсчет от этого числа до нуля.
def count_down(n): for i in range(n,-1,-1): print(i)
Мы определяем count_up
, еще одну функцию Python, которая считает от нуля до заданного числа.
def count_up(n): for i in range(n+1): print(i)
При использовании функции
range()
с синтаксисомrange(start, stop, step)
stop
в конечной точке по умолчанию исключается.– Для обратного отсчета от определенного числа до нуля можно использовать отрицательное значение
step
, равное -1, и установить значениеstop
на -1, чтобы включить ноль.- Точно так же, чтобы сосчитать до
n
, вы должны установить значениеstop
наn + 1
. Поскольку значенияstart
иstep
по умолчанию равны 0 и 1 соответственно, вы можете использоватьrange(n + 1)
для получения последовательности от 0 до n.
Затем мы определяем два потока, thread1
и thread2
, для запуска функций count_down
и count_up
соответственно. Мы добавляем операторы print
и операции sleep
для обеих функций.
При создании объектов потока обратите внимание, что аргументы целевой функции должны быть указаны в виде кортежа — в параметре args
. Поскольку обе функции ( count_down
и count_up
) принимают один аргумент. Вам нужно будет явно вставить запятую после значения. Это гарантирует, что аргумент по-прежнему передается как кортеж, поскольку последующие элементы выводятся как None
.
import threading import time def count_down(n): for i in range(n,-1,-1): print("Running thread1....") print(i) time.sleep(1) def count_up(n): for i in range(n+1): print("Running thread2...") print(i) time.sleep(1) thread1 = threading.Thread(target=count_down,args=(10,)) thread2 = threading.Thread(target=count_up,args=(5,)) thread1.start() thread2.start()
В выводе:
- Функция
count_up
работает вthread2
и считает до 5, начиная с 0. - Функция
count_down
работает вthread1
1, считая от 10 до 0.
# Output Running thread1.... 10 Running thread2... 0 Running thread1.... 9 Running thread2... 1 Running thread1.... 8 Running thread2... 2 Running thread1.... 7 Running thread2... 3 Running thread1.... 6 Running thread2... 4 Running thread1.... 5 Running thread2... 5 Running thread1.... 4 Running thread1.... 3 Running thread1.... 2 Running thread1.... 1 Running thread1.... 0
Вы можете видеть, что thread1
и thread2
выполняются попеременно, так как оба они включают операцию ожидания (сон). Как только функция count_up
закончит считать до 5, thread2
перестанет быть активным. Таким образом, мы получаем вывод, соответствующий только thread1
.
Подводя итоги
В этом руководстве вы узнали, как использовать встроенный в Python модуль многопоточности для реализации многопоточности. Вот краткое изложение основных выводов:
- Конструктор Thread можно использовать для создания объекта потока. Использование threading.Thread(target=<callable>,args=(<tuple of args>)) создает поток, который запускает целевой вызываемый объект с аргументами, указанными в args .
- Программа Python выполняется в основном потоке, поэтому создаваемые вами объекты потока являются дополнительными потоками. Вы можете вызвать функцию active_count() , которая возвращает количество активных потоков в любом случае.
- Вы можете запустить поток, используя метод start() для объекта потока, и дождаться завершения его выполнения с помощью метода join() .
Вы можете написать дополнительные примеры, изменив время ожидания, попробовав другую операцию ввода-вывода и т. д. Обязательно реализуйте многопоточность в своих будущих проектах Python. Удачного кодирования!