You are currently viewing Многопоточность в Python | Набор 1

Многопоточность в Python | Набор 1

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

Давайте сначала разберемся в концепции потока в компьютерной архитектуре.

В вычислительной технике процесс — это экземпляр выполняемой компьютерной программы. Любой процесс состоит из 3 основных компонентов:

  • Исполняемая программа.
  • Связанные данные, необходимые программе (переменные, рабочее пространство, буферы и т. д.)
  • Контекст выполнения программы (состояние процесса)

Поток — это объект в процессе, который может быть запланирован для выполнения. Кроме того, это наименьшая единица обработки, которая может быть выполнена в ОС (операционной системе).

Проще говоря, поток-это последовательность таких инструкций в программе, которые могут выполняться независимо от другого кода. Для простоты можно предположить, что поток-это просто подмножество процесса!

Поток содержит всю эту информацию в блоке управления потоком (TCB):

  • Идентификатор потока: Каждому новому потоку присваивается уникальный идентификатор (TID).
  • Указатель стека: Указывает на стек потока в процессе. Стек содержит локальные переменные в области видимости потока.
  • Счетчик программ: регистр, в котором хранится адрес инструкции, выполняемой в данный момент потоком.
  • Состояние потока: может быть запущено, готово, ждет, запущено или завершено.
  • Набор регистров потока: регистры, назначенные потоку для вычислений.
  • Указатель родительского процесса: Указатель на блок управления процессом (PCB) процесса, на котором живет поток.

Рассмотрим приведенную ниже диаграмму, чтобы понять взаимосвязь между процессом и его потоком:

Многопоточность

В одном процессе может существовать несколько потоков, в которых:

  • Каждый поток содержит свой собственный набор регистров и локальные переменные (хранящиеся в стеке).
  • Все потоки процесса совместно используют глобальные переменные (хранящиеся в куче) и программный код.

Рассмотрим приведенную ниже диаграмму, чтобы понять, как в памяти существует несколько потоков:

Многопоточность определяется как способность процессора выполнять несколько потоков одновременно.

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

Рассмотрим приведенную ниже диаграмму, на которой процесс содержит два активных потока:

Многопоточность в Python

В Python, threading модуль предоставляет очень простой и интуитивно понятный API для создания нескольких потоков в программе.

Рассмотрим простой пример использования модуля threading :

# Python program to illustrate the concept
# of threading
# importing the threading module
import threading

def print_cube(num):
	"""
	function to print cube of given num
	"""
	print("Cube: {}".format(num * num * num))

def print_square(num):
	"""
	function to print square of given num
	"""
	print("Square: {}".format(num * num))

if __name__ == "__main__":
	# creating thread
	t1 = threading.Thread(target=print_square, args=(10,))
	t2 = threading.Thread(target=print_cube, args=(10,))

	# starting thread 1
	t1.start()
	# starting thread 2
	t2.start()

	# wait until thread 1 is completely executed
	t1.join()
	# wait until thread 2 is completely executed
	t2.join()

	# both threads completely executed
	print("Done!")
Square: 100
Cube: 1000
Done!

Давайте попробуем разобраться в приведенном выше коде:

Чтобы импортировать модуль нарезания резьбы, мы делаем:

import threading

Чтобы создать новый поток, мы создаем объект из Класс потоков. Для этого требуются следующие аргументы:

  • target: функция, выполняемая потоком
  • args: аргументы, которые будут переданы целевой функции

В приведенном выше примере мы создали 2 потока с различными целевыми функциями:

t1 = threading.Thread(target=print_square, args=(10,))
t2 = threading.Thread(target=print_cube, args=(10,))
  • Чтобы запустить поток, мы используем метод start класса Thread.
t1.start()
t2.start()
  • Как только потоки запускаются, текущая программа (вы можете думать о ней как о главном потоке) также продолжает выполняться. Чтобы остановить выполнение текущей программы до завершения потока, мы используем метод соединения.
t1.join()
t2.join()

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

Рассмотрим приведенную ниже диаграмму, чтобы лучше понять, как работает вышеприведенная программа:

Рассмотрим приведенную ниже программу на python, в которой мы печатаем имя потока и соответствующий процесс для каждой задачи:

# Python program to illustrate the concept
# of threading
import threading
import os

def task1():
	print("Task 1 assigned to thread: {}".format(threading.current_thread().name))
	print("ID of process running task 1: {}".format(os.getpid()))

def task2():
	print("Task 2 assigned to thread: {}".format(threading.current_thread().name))
	print("ID of process running task 2: {}".format(os.getpid()))

if __name__ == "__main__":

	# print ID of current process
	print("ID of process running main program: {}".format(os.getpid()))

	# print name of main thread
	print("Main thread name: {}".format(threading.current_thread().name))

	# creating threads
	t1 = threading.Thread(target=task1, name='t1')
	t2 = threading.Thread(target=task2, name='t2')

	# starting threads
	t1.start()
	t2.start()

	# wait until all threads finish
	t1.join()
	t2.join()
ID of process running main program: 11758
Main thread name: MainThread
Task 1 assigned to thread: t1
ID of process running task 1: 11758
Task 2 assigned to thread: t2
ID of process running task 2: 11758

Давайте попробуем разобраться в приведенном выше коде:

  • Мы используем os.getpid() функция для получения идентификатора текущего процесса.
print("ID of process running main program: {}".format(os.getpid())) 

Как видно из выходных данных, идентификатор процесса остается одинаковым для всех потоков

  • Мы используем threading.main_thread() функция для получения основного объекта потока. В нормальных условиях основным потоком является поток, из которого был запущен интерпретатор Python. Атрибут name объекта thread используется для получения имени потока.
print("Main thread name: {}".format(threading.main_thread().name))
  • Мы используем threading.current_thread() функция для получения текущего объекта потока.
print("Task 1 assigned to thread: {}".format(threading.current_thread().name)) 

Приведенная ниже диаграмма проясняет вышеприведенную концепцию:

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