Большинство компьютеров тратит массу времени, ничего не делая. Если запустить системный монитор и посмотреть на уровень загрузки процессора, вы поймете, что я имею в виду: он очень редко достигает 100%, даже если выполняется несколько программ одновременно.[XIII] Просто в программном обеспечении существует очень много задержек — доступ к диску, сетевой трафик, запросы к базам данных, ожидание нажатия клавиши пользователем и тому подобное. Фактически большая часть мощности современных процессоров большую часть времени не используется: более быстрые процессоры дают ускорение во время пиков потребности в производительности, но значительная часть их мощности в целом может оказаться невостребованной.
Еще на заре эпохи компьютеров программисты поняли, что могут воспользоваться такой неиспользуемой вычислительной мощностью, выполняя одновременно несколько программ. Если распределить процессорное время среди множества задач, его мощность не будет тратиться впустую, пока некоторая конкретная задача ждет осуществления внешнего события. Такая технология обычно называется параллельной обработкой (или, иногда, «мультиобработкой» или даже «многозадачностью»), потому что возникает впечатление одновременного выполнения нескольких заданий параллельно во времени. Это одна из центральных идей современных операционных систем, на основе которой возникло представление о компьютерных интерфейсах с несколькими активными окнами, воспринимаемое нами теперь, как нечто само собой разумеющееся. Даже внутри одной программы разделение обработки на ряд параллельно выполняющихся заданий может увеличить быстродействие системы в целом, во всяком случае по меркам внешних часов.
Столь же важно для современных систем обладание быстрой реакцией на действия пользователя, независимо от объема работы, выполняемой за кулисами. Обычно недопустимо, чтобы программа зависала при выполнении запроса. Взгляните, например, на пользовательский интерфейс клиента электронной почты: обрабатывая запрос на получение почты с сервера, программа должна загрузить электронные письма с сервера через сеть. Если почты достаточно много, а соединение с Интернетом достаточно медленное, для завершения этого этапа может потребоваться несколько минут. Но по ходу выполнения задачи загрузки программа в целом не должна останавливаться — она по-прежнему должна реагировать на запросы обновления экрана, щелчки мышью и так далее.
И здесь на помощь приходит параллельная обработка. Выполняя такие долговыполняющиеся задачи параллельно с остальной частью программы, система в целом может сохранить способность реагировать на действия пользователя независимо от того, насколько занятыми оказываются отдельные ее части. Более того, модель параллельной обработки является вполне естественной для структурирования таких и некоторых иных программ — некоторые задачи легче проще проектировать и реализовывать как набор программных компонентов, действующих независимо и параллельно.
Существует два основных способа реализации одновременного выполнения задач в Python — ветвление процессов (forks) и порожденные потоки (threads) выполнения. Функционально для организации параллельного выполнения программного кода на языке Python оба способа используют службы операционной системы. Процедурно они существенно отличаются в смысле интерфейсов, переносимости и организации взаимодействий между заданиями. Например, на момент написания данной книги возможность прямого ветвления процессов не поддерживалась стандартной реализацией Python для Windows (однако такая поддержка присутствует в версии Python для Cygwin).
Напротив, поддержка потоков выполнения в Python реализована на всех основных платформах. Кроме того, семейство функций os.spawn обеспечивает дополнительные способы запуска способом, не зависящим от типа платформы, — напоминающим ветвление процессов. Для запуска программ переносимым способом, с помощью команд оболочки, также можно использовать функции os.popen, os.system и модуль subprocess, с которыми мы познакомились в главах 2 и 3. Новейший пакет multiprocessing предоставляет дополнительные переносимые способы запуска процессов.
В данной главе мы продолжим рассмотрение системных интерфейсов, доступных программистам на языке Python, исследуем встроенные инструменты для параллельного запуска заданий и обмена информацией с этими заданиями. В некотором смысле мы приступили к этому раньше — функции os.system, os. popen и модуль subprocess, которые мы изучали и использовали в предыдущих трех главах, обеспечивают переносимый способ порождения программ командной строки и обмена информацией с ними. Однако здесь мы не собираемся повторять полное описание этих инструментов.
Вместо этого мы сделаем упор на знакомстве с более прямо относящимися к теме приемами, такими как ветвление процессов, потоки, каналы, сигналы, сокеты и другими, и на использовании встроенных инструментов языка Python, поддерживающими их, такими как функция os.fork и модули threading, queue и multiprocessing. В следующей главе (и в оставшейся части книги) мы будем использовать эти приемы в примерах действующих программ, поэтому, прежде чем двигаться вперед, необходимо усвоить основы.
Одно предварительное замечание: процессы, потоки и механизмы взаимодействий между процессами, которые мы будем исследовать в этой главе, являются основными инструментами организации параллельной обработки в сценариях на языке Python, однако существует множество сторонних инструментов, предлагающих дополнительные возможности, способные обслуживать расширенные или углубленные потребности. В качестве примера приведу систему MPI для Python, позволяющую в сценариях на языке Python использовать стандартный интерфейс передачи сообщений (Message Passing Interface, MPI), дающий возможность организовать взаимодействие между процессами различными способами (подробности ищите в Интернете). Изучение подобных расширений выходит далеко за рамки этой книги, тем не менее большинство расширенных техник, с которыми вы можете встретиться в будущем, также опираются на основы параллельной обработки, которые мы будем исследовать здесь.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011