Поток – это последовательность инструкций, которые выполняются параллельно с другими потоками внутри одного процесса. Каждый процесс запускает как минимум один, основной, поток. Процесс может создавать столько потоков, сколько позволяют настройки операционной системы, либо пока не закончится оперативная память.
При запуске процесса, операционная система выделяет для него область памяти и другие ресурсы, а основной поток использует их. Если в процессе запущен один поток, то за один момент времени выполняется только одна операция. Если потоков несколько, то они могут выполнять параллельно свои операции, но число одновременных операций не может быть больше числа ядер в процессоре.
Каждому потоку выделяется свой стек – область оперативной памяти, а также у потоков есть общий доступ к общей памяти процесса. Например, если в общей памяти есть список с данными, то его можно читать и изменять из созданных в рамках процесса потоков.
В python работа с потоками производится с помощью модуля threading. Рассмотрим простейший пример создания потока.
1import time
2from threading import Thread, current_thread
3
4def foo(text):
5 time.sleep(2)
6 print('[foo] Current thread', current_thread().name)
7 print('msg:', text)
8
9t = Thread(target=foo, args=('Hello world',))
10t.start()
11
12print('[main] Current thread', current_thread().name)
13print('Waiting for thread end...')
14
15t.join()
16
17print('Threads finished')
Пример вывода результата выполнения в консоль:
1[main] Current thread MainThread
2Waiting for thread end...
3[foo] Current thread foo-thread
4msg: Hello world
5Threads finished
Рассмотрим пример подробнее. Для начала объявляем функцию foo, которая будет запускаться в отдельном потоке. Затем с помощью класса Thread создаем экземпляр потока, передав в параметр target callable объект (в нашем случае функцию foo), а также кортеж аргументов функции в параметр args. Параметр name позволяет дать потоку некое осмысленное имя. Метод start() запускает поток. Метод join() позволяет дождаться завершения выполнения потоков в программе.
Также в модуле threading есть функция current_thread(), которая позволяет получить экземпляр текущего потока, связанный с контекстом выполнения callable объекта. Добавив вывод имени потока, можно увидеть в каких потоках происходит выполнение кода.
Потоки потребляют меньше ресурсов, а также могут работать с любыми типами данных, при передаче параметров, не требуя из сериализации, в отличае от процессов.
Таким образом и работает поток. Функция выполняется независимо от основного кода скрипта или наоборот.
Если не добавлять в код основной программы инструкцию t.join(), то будет происходить завершение основного кода выполнения, но при этом, если какие то потоки будут продолжать работу, то общее выполнение программы завершиться, только после выполнения всех потоков.
Такое поведение не всегда желательно, так как потоки для выполнения каких-либо фоновых задач (например, монтирование устройств) могут выполняться постоянно. Чтобы указать основной программе, что процесс фоновый и не нужно ждать его завершения, при создании потока нужно добавить параметр daemon=True.
1t = Thread(target=foo, args=(‘Hello world’, ), daemon=True)
В этом случае выполнение потока прекратиться сразу же при выполнении основной программы, не дожидаясь его завершения.
Очень часто бывает, что необходимо запускать несколько процессов одновременно и управлять ими. Для этого используется класс ThreadPoolExecutor из модуля concurrent.futures. О работе с ним можно почитать в нашей статье.
Рассмотрим еще раз основные достоинста и недостатки потоков в python.
Достоинства:
Недостатки