Найти в Дзене
programmer's notes (python and more)

Программирование на Python. Многозадачность. Библиотека многопроцессорной обработки данных multiprocessing. Введение

Доброго времени суток, читатели, зрители моего канала programmer's notes. Не забывайте подписываться и писать свои комментарии к моим статьям и видео.

Модуль многопроцессорной обработки данных multiprocessing в Python. Модуль psutil

Наконец мы дошли до этой замечательной библиотеки поддержки многозадачности. Библиотека multiprocessing позволяет использовать в программе сразу несколько процессоров компьютера. Этим она отличается от других библиотек Python (см. например os.fork(), subprocess, threading), которые такой гарантии не дают, а используют простое переключение, которое осуществляет планировщик задач. Впрочем, мы никогда не знаем, когда операционная система начинает для запуска задач использовать другие процессоры. Для этого требуется дополнительное исследование, которым я пока заниматься не буду. Но библиотека multiprocessing позволяет программно указывать системе, что для запуска выполнения разных процессов следует использовать разные процессоры. А это уже шаг к тому, что в случае сложной большой задачи, её можно попытаться разделить на несколько простых (распараллелить) и выполнить каждую на отдельном процессоре (ядре), что при правильном подходе должно дать увеличение производительности.

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

Текст программы см. ниже
Текст программы см. ниже
primer173.py

Результат выполнения (на моём домашнем компьютере)

Количество процессоров: 12
Количество доступных процессоров:  12
Вывод:
ForkPoolWorker-2   2 20
ForkPoolWorker-1   0 10
ForkPoolWorker-3   3 30
ForkPoolWorker-3   3 90
ForkPoolWorker-4   4 40
ForkPoolWorker-5   10 50
ForkPoolWorker-6   8 60
ForkPoolWorker-7   1 70
ForkPoolWorker-9   6 100
ForkPoolWorker-8   5 80
ForkPoolWorker-11   9 110
ForkPoolWorker-12   4 120
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]

Комментарии к программе

  1. Обратим внимание в начале на метод multiprocessing.cpu_count(). Он выводит количество процессоров (ядер) на нашем компьютере. На моём компьютере дома их 12, на рабочем компьютере сегодня проверил, их оказалось всего 4.
  2. Но количество процессоров ещё не означает, что все они могут быть использованы. В системе могут производится какие-то действия, в которых система посчитала нужным использовать несколько процессоров. Есть замечательный метод os.sched_getaffinity(), с помощью которого можно узнать, на сколько процессоров может рассчитывать данный процесс. Метод имеет входной параметр, который должен быть равен идентификатору процесса (PID). Если параметр равен 0, выдаются данные для текущего процесса. Данный метод выводит не количество, а список процессоров (начиная с процессора с номером 0). Так что в программе мы выводим длину списка.
  3. Далее используется метод multiprocessing.Pool(n2), который задает пул, состоящий из n2 процессов, которые пока не определены. В результате создается объект пула, методами которого можно пользоваться.
  4. Ну, а запуск пула осуществляется методом pool.map(). Этот метод в точности соответствует известной функции map(). Только первым параметрам у нас стоит имя функции, которая будет запущена с элементом итерируемого объекта в качестве параметра. При чём на основе этой функции создается новый процесс, а не поток (sic!). Таким образом создаётся столько процессов, сколько сколько итераций в объекте, который идёт вторым параметром. Мы задали количество элементов списка равное количество процессоров.
  5. В каждом процессе производится вычисление (переменная a) на основе полученного параметра. Напомню, что параметры это элементы списка ls.
  6. Кроме полученного значения выводится также имя текущего процесса (multiprocessing.current_process().name) и номер процессора, который выполняет данный процесс (psutil.Process().cpu_num()). Кстати ничего не сказал о библиотеке psutil, но пока примем её как данность. А в ближайшее время придётся посвятить ей отдельную статью.
  7. Из представленных выше пунктов становится ясно, что выводит программа. Но стоит пояснить ещё два момент. 1. Как мы видим порядок выполнения процессов произвольный. При втором запуске этот порядок может быть другим. 2. Не все процессоры оказались задействованы. В примере "не сработал" процессор с номером 7. Но здесь никакого противоречие. Это происходит по одной простой причине. Один из процессов закончил свою работу и его процессор освободился и система посчитала возможным снова использовать его. Это процессор с номером 3.

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

Хорошего программирования. Оставляйте свои комментарии, не забывайте про лайки и подписывайтесь на мой канал programmer's notes.

Предпочитаю всегда использовать для решения задач только один свой процессор
Предпочитаю всегда использовать для решения задач только один свой процессор
Многозадачность в Python | programmer's notes (python and more) | Дзен