Учебники

Параллелизм в Python — многопроцессорность

В этой главе мы сосредоточимся больше на сравнении между многопроцессорностью и многопоточностью.

многопроцессорная обработка

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

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

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

Следующая таблица показывает некоторые важные различия между ними —

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

Устранение влияния глобальной блокировки интерпретатора (GIL)

При работе с параллельными приложениями в Python есть ограничение, называемое GIL (Global Interpreter Lock) . GIL никогда не позволяет нам использовать несколько ядер CPU, и поэтому мы можем сказать, что в Python нет настоящих потоков. GIL — мьютекс — блокировка взаимного исключения, которая делает вещи безопасными. Другими словами, мы можем сказать, что GIL препятствует параллельному выполнению кода Python несколькими потоками. Блокировка может удерживаться только одним потоком за раз, и если мы хотим выполнить поток, он должен сначала получить блокировку.

Используя многопроцессорность, мы можем эффективно обойти ограничение, вызванное GIL —

  • Используя многопроцессорность, мы используем возможности нескольких процессов и, следовательно, мы используем несколько экземпляров GIL.

  • В связи с этим нет ограничений на выполнение байт-кода одного потока в наших программах одновременно.

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

В связи с этим нет ограничений на выполнение байт-кода одного потока в наших программах одновременно.

Запуск процессов в Python

Следующие три метода могут быть использованы для запуска процесса в Python внутри модуля многопроцессорной обработки:

  • вилка
  • Порождать
  • Forkserver

Создание процесса с помощью Fork

Команда Fork — это стандартная команда в UNIX. Он используется для создания новых процессов, называемых дочерними процессами. Этот дочерний процесс выполняется одновременно с процессом, называемым родительским процессом. Эти дочерние процессы также идентичны своим родительским процессам и наследуют все ресурсы, доступные родительскому процессу. Следующие системные вызовы используются при создании процесса с помощью Fork —

  • fork () — это системный вызов, обычно реализованный в ядре. Он используется для создания копии процесса.

  • getpid () — этот системный вызов возвращает идентификатор процесса (PID) вызывающего процесса.

fork () — это системный вызов, обычно реализованный в ядре. Он используется для создания копии процесса.

getpid () — этот системный вызов возвращает идентификатор процесса (PID) вызывающего процесса.

пример

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

import os

def child():
   n = os.fork()
   
   if n > 0:
      print("PID of Parent process is : ", os.getpid())

   else:
      print("PID of Child process is : ", os.getpid())
child()

Выход

PID of Parent process is : 25989
PID of Child process is : 25990

Создание процесса с помощью Spawn

Spawn означает начать что-то новое. Следовательно, порождение процесса означает создание нового процесса родительским процессом. Родительский процесс продолжает свое выполнение асинхронно или ожидает, пока дочерний процесс не завершит свое выполнение. Выполните следующие шаги для запуска процесса —

  • Импорт многопроцессорного модуля.

  • Создание объекта процесса.

  • Запуск процесса с помощью вызова метода start () .

  • Дождитесь, пока процесс завершит свою работу, и выйдите, вызвав метод join () .

Импорт многопроцессорного модуля.

Создание объекта процесса.

Запуск процесса с помощью вызова метода start () .

Дождитесь, пока процесс завершит свою работу, и выйдите, вызвав метод join () .

пример

Следующий пример скрипта Python помогает в порождении трех процессов

import multiprocessing

def spawn_process(i):
   print ('This is process: %s' %i)
   return

if __name__ == '__main__':
   Process_jobs = []
   for i in range(3):
   p = multiprocessing.Process(target = spawn_process, args = (i,))
      Process_jobs.append(p)
   p.start()
   p.join()

Выход

This is process: 0
This is process: 1
This is process: 2

Создание процесса с помощью Forkserver

Механизм Forkserver доступен только на тех выбранных платформах UNIX, которые поддерживают передачу файловых дескрипторов по каналам Unix. Рассмотрим следующие моменты, чтобы понять работу механизма Forkserver —

  • Сервер создается с использованием механизма Forkserver для запуска нового процесса.

  • Затем сервер получает команду и обрабатывает все запросы на создание новых процессов.

  • Для создания нового процесса наша программа на Python отправит запрос в Forkserver и создаст для нас процесс.

  • Наконец, мы можем использовать этот новый созданный процесс в наших программах.

Сервер создается с использованием механизма Forkserver для запуска нового процесса.

Затем сервер получает команду и обрабатывает все запросы на создание новых процессов.

Для создания нового процесса наша программа на Python отправит запрос в Forkserver и создаст для нас процесс.

Наконец, мы можем использовать этот новый созданный процесс в наших программах.

Демонстрационные процессы в Python

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

пример

Здесь мы используем тот же пример, что и в потоках демона. Единственное отличие — это изменение модуля с многопоточности на многопроцессорность и установка флага демона в значение true. Тем не менее, будет изменение в выходе, как показано ниже —

import multiprocessing
import time

def nondaemonProcess():
   print("starting my Process")
   time.sleep(8)
   print("ending my Process")
def daemonProcess():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonProcess = multiprocessing.Process(target = nondaemonProcess)
   daemonProcess = multiprocessing.Process(target = daemonProcess)
   daemonProcess.daemon = True
   nondaemonProcess.daemon = False
   daemonProcess.start()
   nondaemonProcess.start()

Выход

starting my Process
ending my Process

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

Завершение процессов в Python

Мы можем немедленно завершить или завершить процесс с помощью метода terminate () . Мы будем использовать этот метод для завершения дочернего процесса, который был создан с помощью функции, непосредственно перед завершением его выполнения.

пример

import multiprocessing
import time
def Child_process():
   print ('Starting function')
   time.sleep(5)
   print ('Finished function')
P = multiprocessing.Process(target = Child_process)
P.start()
print("My Process has terminated, terminating main thread")
print("Terminating Child Process")
P.terminate()
print("Child Process successfully terminated")

Выход

My Process has terminated, terminating main thread
Terminating Child Process
Child Process successfully terminated

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

Определение текущего процесса в Python

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

import multiprocessing
print(multiprocessing.current_process().pid)

пример

Следующий пример скрипта Python помогает узнать PID основного процесса, а также PID дочернего процесса —

import multiprocessing
import time
def Child_process():
   print("PID of Child Process is: {}".format(multiprocessing.current_process().pid))
print("PID of Main process is: {}".format(multiprocessing.current_process().pid))
P = multiprocessing.Process(target=Child_process)
P.start()
P.join()

Выход

PID of Main process is: 9401
PID of Child Process is: 9402

Использование процесса в подклассе

Мы можем создавать потоки, подклассифицируя класс threading.Thread . Кроме того, мы также можем создавать процессы, подклассифицируя класс multiprocessing.Process . Для использования процесса в подклассе нам необходимо учитывать следующие моменты:

  • Нам нужно определить новый подкласс класса Process .

  • Нам нужно переопределить класс _init_ (self [, args]) .

  • Нам нужно переопределить метод run (self [, args]), чтобы реализовать какой процесс

  • Нам нужно запустить процесс, вызвав метод start () .

Нам нужно определить новый подкласс класса Process .

Нам нужно переопределить класс _init_ (self [, args]) .

Нам нужно переопределить метод run (self [, args]), чтобы реализовать какой процесс

Нам нужно запустить процесс, вызвав метод start () .

пример

import multiprocessing
class MyProcess(multiprocessing.Process):
   def run(self):
   print ('called run method in process: %s' %self.name)
   return
if __name__ == '__main__':
   jobs = []
   for i in range(5):
   P = MyProcess()
   jobs.append(P)
   P.start()
   P.join()

Выход

called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5

Модуль многопроцессорной обработки Python — класс пула

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

применить () метод

Этот метод аналогичен методу .submit () класса .ThreadPoolExecutor. Он блокируется, пока результат не будет готов.

apply_async () метод

Когда нам нужно параллельное выполнение наших задач, тогда мы должны использовать метод apply_async () для отправки задач в пул. Это асинхронная операция, которая не блокирует основной поток, пока не будут выполнены все дочерние процессы.

метод map ()

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

метод map_async ()

Это вариант метода map (), так как apply_async () относится к методу apply () . Возвращает объект результата. Когда результат становится готовым, к нему применяется вызываемый элемент. Призыв должен быть завершен немедленно; в противном случае поток, обрабатывающий результаты, будет заблокирован.

пример

Следующий пример поможет вам реализовать пул процессов для параллельного выполнения. Простое вычисление квадрата числа было выполнено путем применения функции square () с помощью метода multiprocessing.Pool . Затем pool.map () использовался для отправки 5, потому что input — это список целых чисел от 0 до 4. Результат будет сохранен в p_outputs и напечатан.