Главная страница
Навигация по странице:

  • Параллельное программирование и Python

  • Process ([ group [, target [, name [, args [, kwargs ]]]]])

  • Взаимодействия между процессами

  • справочник по Python. мм isbn 9785932861578 9 785932 861578


    Скачать 4.21 Mb.
    Названиемм isbn 9785932861578 9 785932 861578
    Анкорсправочник по Python
    Дата08.05.2022
    Размер4.21 Mb.
    Формат файлаpdf
    Имя файлаBizli_Python-Podrobnyy-spravochnik.440222.pdf
    ТипСправочник
    #518195
    страница44 из 82
    1   ...   40   41   42   43   44   45   46   47   ...   82
    Глава
    20
    .
    Потоки и многозадачность
    В этой главе описываются модули и приемы программирования, использу- емые при разработке многопоточных приложений на языке Python. В чис-
    Python. В чис-
    . В чис- ло рассматриваемых тем входят: потоки управления, обмен сообщениями, многопоточная обработка данных и сопрограммы. Прежде чем перейти к рассмотрению библиотечных модулей, познакомимся с некоторыми основными понятиями.
    Основные понятия
    Выполняющаяся программа называется процессом. Каждый процесс обла- дает некоторыми параметрами, характеризующими его состояние, вклю- чая объем занимаемой памяти, список открытых файлов, программный счетчик, который ссылается на очередную выполняемую инструкцию, и стек вызовов, используемый для хранения локальных переменных функ- ций. Обычно процесс выполняет инструкции одну за другой, в единствен- ном потоке управления, который иногда называют главным потоком про- цесса. В каждый конкретный момент программа делает что-то одно.
    Программа может создавать новые процессы с помощью библиотечных функций, таких как представленные в модуле os или subprocess (например, os.fork()
    , subprocess.Popen() и другие). Такие процессы называют дочерними
    процессами
    . Они выполняются совершенно независимо, и каждый из них имеет свои собственные характеристики и главный поток управления. Так как дочерние процессы являются независимыми, они выполняются парал- лельно с родительским процессом. То есть родительский процесс, создав- ший дочерние процессы, может выполнять свои операции, тогда как дочер- ние процессы будет за кулисами выполнять свою работу.
    Несмотря на то что процессы изолированы друг от друга, тем не менее они могут обмениваться информацией, используя механизмы взаимодей-
    ствия процессов
    (Interprocess Communication, IPC). Одной из распростра-
    Interprocess Communication, IPC). Одной из распростра-
    Communication, IPC). Одной из распростра-
    Communication, IPC). Одной из распростра-
    , IPC). Одной из распростра-
    IPC). Одной из распростра-
    ). Одной из распростра- ненных форм взаимодействия процессов является обмен сообщениями.
    Под сообщением в данном случае понимается простой буфер двоичных

    Основные понятия
    517
    байтов. Для приема и передачи сообщений через каналы ввода-вывода, например неименованные каналы или сетевые соединения, используются простейшие операции, такие как send() и recv(). Другой, реже используе- мый механизм взаимодействий, основан на использовании отображаемых областей памяти (смотрите описание модуля mmap). Благодаря возможно- сти отображения в память процессы могут создавать разделяемые обла- сти памяти. Изменения в этих областях будут доступны всем процессам, имеющим к ним доступ.
    Возможность создания дочерних процессов может использоваться при- ложениями для выполнения сразу нескольких задач, когда каждый про- цесс отвечает за свою часть работы. Однако существует и другой подход к разделению работы на задачи, основанный на использовании несколь- ких потоков управления. Поток управления напоминает процесс тем, что он выполняет собственную последовательность инструкций и имеет свой стек вызовов. Разница состоит в том, что потоки выполняются в пределах процесса, создавшего их, и они совместно используют данные и системные ресурсы, выделенные процессу. Потоки удобно использовать, когда в при- ложении необходимо реализовать одновременное выполнение нескольких задач, но при этом имеется значительный объем информации, которая должна быть доступна всем потокам.
    Когда одновременно выполняется несколько процессов или потоков управ- ления, за распределение процессорного времени между ними отвечает операционная система. Она выделяет каждому процессу (или потоку) не- большой квант времени и быстро переключается между активными зада- чами, отдавая каждой из них определенное количество тактов процессора.
    Например, если в системе имеется 10 активных процессов, операционная система будет выделять каждому из них примерно 1/10 процессорного вре- мени и будет быстро переключаться между ними. В системах, где имеется более одного ядра процессора, операционная система сможет планировать выполнение процессов так, чтобы все ядра процессоров оказались заняты примерно поровну параллельным выполнением процессов.
    Разработка программ, использующих преимущества параллельного вы- полнения задач, является несомненно сложным делом. Основная слож- ность заключается в синхронизации и обеспечении доступа к совместно используемым данным. В частности, попытка изменить некоторые данные одновременно из нескольких потоков может привести к их повреждению и к нарушению целостности состояния программы (формально эта пробле- ма известна как гонка за ресурсами). Чтобы избавиться от этой проблемы, в многопоточных программах необходимо выделить критические участ- ки программного кода и обеспечить их выполнение под защитой взаимо- исключающих блокировок или других, похожих механизмов синхрониза- ции. Например, если в разных потоках одновременно появится необходи- мость выполнить запись в один и тот же файл, для синхронизации их дей- ствий можно воспользоваться взаимоисключающей блокировкой, чтобы, пока один из потоков выполняет запись, остальные ожидали бы заверше- ния операции и только после этого получали доступ к файлу. Реализация синхронизации подобного рода обычно выглядит, как показано ниже:

    518
    Глава 20. Потоки и многозадачность write_lock = Lock()
    ...
    # Критический участок, где выполняется запись write_lock.acquire()
    f.write(“Here’s some data.\n”)
    f.write(“Here’s more data.\n”)
    ...
    write_lock.release()
    Существует шутка, которая приписывается Джейсону Уиттингтону (Jason
    Whittington): «Зачем многопоточный цыпленок пересекает дорогу? на Что- бы другую сторону. перейти». Эта шутка символизирует типичные пробле- мы, связанные с синхронизацией и многопоточным программированием.
    Если вы в задумчивости почесываете затылок и говорите себе: «Я ничего не понял», – то вам стоит почитать дополнительную литературу, прежде чем продолжать чтение этой главы.
    Параллельное программирование и Python
    Python обеспечивает поддержку механизма обмена сообщениями и позво- обеспечивает поддержку механизма обмена сообщениями и позво- ляет создавать многопоточные программы в большинстве систем. Боль- шинство программистов знакомы с интерфейсом потоков, но не многие знают, что потоки управления в языке Python имеют существенные огра-
    Python имеют существенные огра- имеют существенные огра- ничения. Несмотря на наличие минимальной поддержки многопоточных приложений, интерпретатор Python использует внутреннюю глобальную блокировку интерпретатора (Global Interpreter Lock, GIL), из-за чего в каж- дый конкретный момент времени может выполняться только один поток.
    Вследствие этого программы на языке Python могут выполняться только на одном процессоре, независимо от их количества в системе. Наличие гло- бальной блокировки GIL часто становится источником жарких дебатов в со-
    GIL часто становится источником жарких дебатов в со- часто становится источником жарких дебатов в со- обществе Python, тем не менее весьма маловероятно, что она будет устране-
    Python, тем не менее весьма маловероятно, что она будет устране-
    , тем не менее весьма маловероятно, что она будет устране- на в обозримом будущем.
    Присутствие блокировки GIL напрямую влияет на количество програм-
    GIL напрямую влияет на количество програм- напрямую влияет на количество програм- мистов, занимающихся проблемами разработки многопоточных приложе- ний. Вообще говоря, если приложение в основном занимается операция- ми ввода-вывода, оно, как правило, является прекрасным кандидатом на реализацию в виде многопоточного приложения, потому что наличие до- полнительных процессоров практически не дает преимуществ программе, которая большую часть времени проводит в ожидании событий. С другой стороны, разделение приложения, выполняющего объемные вычисления, на несколько потоков не только не дает никаких преимуществ, но и снизит производительность программы (причем снижение производительности окажется намного существеннее, чем вы могли бы предположить). Подоб- ные программы лучше реализовать в виде нескольких процессов и обеспе- чить взаимодействие между ними с привлечением механизма обмена со- общениями.
    Даже те программисты, которые используют механизм потоков, зачастую находят весьма странными их поведение при масштабировании. Напри- мер, многопоточный сетевой сервер может иметь прекрасную производи-

    Модуль multiprocessing
    519
    тельность при одновременной работе 100 потоков и просто ужасную, когда их число увеличивается до 10 000. Вообще говоря, не стоит писать програм- мы, запускающие 10 000 потоков, потому что каждый поток потребляет ресурсы системы и увеличивает нагрузку, связанную с необходимостью переключения контекста потоков, установки блокировок и использования других механизмов, интенсивное использование которых приводит к уве- личению нагрузки (не говоря уже о том, что все потоки вынуждены выпол- няться на единственном процессоре). Подобные проблемы часто решаются за счет реструктуризации приложения и использования систем асинхрон- ной обработки событий. Например, главный цикл событий может просма- тривать все каналы ввода-вывода, используя модуль select, и передавать асинхронные события большой коллекции обработчиков ввода-вывода.
    Подобный подход был положен в основу некоторых библиотечных моду- лей, таких как asyncore, а также популярных сторонних модулей, таких как Twisted (http://twistedmatrix/com).
    Забегая вперед, скажу, что, приступая к разработке многопоточных при- ложений на языке Python, в первую очередь необходимо выбрать механизм обмена сообщениями. При работе с потоками часто рекомендуется органи- зовать приложение как коллекцию независимых потоков, обменивающих- ся данными с помощью очередей сообщений. Такой подход меньше подвер- жен ошибкам, потому что он снижает необходимость использования бло- кировок и других механизмов синхронизации. Кроме того, обмен сообще- ниями легко и естественно распространяется на сетевые взаимодействия и распределенные системы. Например, если имеется часть программы, которая выполняется в виде отдельного потока и принимает сообщения, эту часть затем можно оформить в виде отдельного процесса или перенести на другой компьютер и посылать ей сообщения через сетевое соединение.
    Кроме того, абстракция механизма обмена сообщениями легко совмеща- ется с такими понятиями языка Python, как сопрограммы. Например, со-
    Python, как сопрограммы. Например, со-
    , как сопрограммы. Например, со- программа – это функция, которая принимает и обрабатывает сообщения, передаваемые ей. Поэтому, приняв за основу механизм обмена сообщения- ми, вы обнаружите, что в состоянии писать программы, обладающие суще- ственной гибкостью.
    В оставшейся части главы будут рассматриваться различные библиотеч- ные модули, обеспечивающие поддержку многопоточного программирова- ния. В конце главы приводится более подробная информация о наиболее типичных идиомах программирования.
    Модуль multiprocessing
    Модуль multiprocessing предоставляет поддержку запуска задач в виде до- черних процессов, взаимодействий между ними и совместного использо- вания данных, а также обеспечивает различные способы синхронизации.
    Программный интерфейс модуля имитирует программный интерфейс по- токов, реализованный в модуле threading. Однако важное отличие от пото- ков состоит в том, что процессы не имеют совместно используемых данных.
    То есть, если процесс изменит данные, эти изменения будут носить локаль- ный характер для данного процесса.

    520
    Глава 20. Потоки и многозадачность
    М
    одуль multiprocessing обладает весьма широкими возможностями, что де- лает его одной из самых крупных и самых сложных встроенных библио- тек. Здесь невозможно во всех подробностях описать каждую особенность модуля, тем не менее мы рассмотрим самые основные его части, наряду с примерами использования. Опытные программисты смогут взять за осно- ву эти примеры и распространить используемые в них приемы на более сложные задачи.
    Процессы
    Все функциональные возможности, имеющиеся в модуле multiprocessing, направлены на работу с процессами, которые описываются следующим классом.
    Process ([group [, target [, name [, args [, kwargs]]]]])
    Класс, представляющий задачу, запущенную в дочернем процессе. Па- раметры всегда должны передаваться конструктору в виде именованных аргументов. В аргументе target передается объект, поддерживающий воз- можность вызова, который будет выполнен при запуске процесса, в ар- гументе args передается кортеж позиционных аргументов для функции
    target
    , а в аргументе kwargs – словарь именованных аргументов для объекта
    target
    . Если опустить аргументы args и kwargs, объект target будет вызван без аргументов. В аргументе name передается строка с описательным име- нем процесса. Аргумент group не используется и всегда принимает значение
    None
    . Он присутствует лишь для полноты имитации создания потоков с по- мощью модуля threading.
    Экземпляр p класса Process обладает следующими методами:
    p.is_alive()
    Возвращает True, если процесс p продолжает работу.
    p.join([timeout])
    Ожидает завершения процесса p. Аргумент timeout определяет максималь- ный период ожидания. Присоединяться к процессу, в ожидании его завер- шения, можно неограниченное число раз, но будет ошибкой, если процесс попытается присоединиться к себе самому.
    p.run()
    Метод, который вызывается в дочернем процессе при его запуске. По умол- чанию вызывает объект target, который был передан конструктору класса
    Process
    . При желании можно создать свой класс, производный от класса
    Process
    , и определить в нем собственную реализацию метода run().
    p.start()
    Запускает процесс. Запускает дочерний процесс и вызывает в нем метод
    p.run()
    p.terminate()
    Принудительно завершает процесс. При вызове этого метода дочерний про- цесс завершается немедленно, без выполнения каких-либо заключитель- ных процедур. Если процесс p создал свои дочерние процессы, они превра-

    Модуль multiprocessing
    521
    тятся в «зомби». Этот метод требует осторожного обращения. Если процесс
    p
    установил блокировку или вовлечен во взаимодействия с другими про- цессами, его принудительное завершение может вызвать взаимоблокиров- ку процессов или повреждение данных.
    Экземпляр p класса Process обладает также следующими атрибутами:
    p.authkey
    Ключ аутентификации процесса. Если значение не было определено явно, в этот атрибут записывается 32-символьная строка, сгенерированная функцией os.urandom(). Назначение этого ключа состоит в том, чтобы обе- спечить безопасность низкоуровневых операций взаимодействия между процессами, которые выполняются через сетевые соединения. Взаимодей- ствия через такие соединения будут возможны, только если с обоих концов используется один и тот же ключ аутентификации.
    p.daemon
    Логический флаг, указывающий – будет ли дочерний процесс демониче- ским. Демонический
    1
    процесс завершается автоматически, вместе с про- цессом Python, создавшим его. Кроме того, демонический процесс лишен возможности создавать дочерние процессы. Значение атрибута p.daemon должно устанавливаться до вызова метода p.start().
    p.exitcode
    Целочисленный код завершения процесса. Если процесс продолжает вы- полняться, этот атрибут будет содержать значение None. Отрицательное значение –N означает, что процесс был завершен сигналом N.
    p.name
    Имя процесса.
    p.pid
    Целочисленный идентификатор процесса.
    Следующий пример демонстрирует, как создавать и запускать функции
    (или другие вызываемые объекты) в отдельном процессе:
    import multiprocessing import time
    ёё
    def clock(interval):
    while True:
    print(“The time is %s” % time.ctime())
    time.sleep(interval)
    ёё
    if __name__ == ‘__main__’:
    p = multiprocessing.Process(target=clock, args=(15,))
    p.start()
    1
    Здесь понятие демонический не имеет никакого отношения к понятию демона
    (фонового процесса) в UNIX. Под демоническим процессом понимается процесс, который просто автоматически завершается вместе с родительским процес- сом. – Прим. перев.

    522
    Глава 20. Потоки и многозадачность
    Следующий пример демонстрирует, как определить свой класс, производ- ный от класса Process:
    import multiprocessing import time
    ёё
    class ClockProcess(multiprocessing.Process):
    def __init__(self,interval):
    multiprocessing.Process.__init__(self)
    self.interval = interval def run(self):
    while True:
    print(“The time is %s” % time.ctime())
    time.sleep(self.interval)
    ёё
    if __name__ == ‘__main__’:
    p = ClockProcess(15)
    p.start()
    В обоих примерах дочерние процессы должны выводить текущее время каждые 15 секунд. Важно заметить, что для обеспечения межплатформен- ной совместимости новые процессы должны создаваться только основной программой, как показано в примерах. В UNIX это условие можно не со-
    UNIX это условие можно не со- это условие можно не со- блюдать, но в Windows оно является обязательным. Кроме того, нужно от-
    Windows оно является обязательным. Кроме того, нужно от- оно является обязательным. Кроме того, нужно от- метить, что в Windows предыдущие примеры необходимо запускать в ко-
    Windows предыдущие примеры необходимо запускать в ко- предыдущие примеры необходимо запускать в ко- мандной оболочке (command.exe), а не в интегрированной среде разработки на языке Python, такой как IDLE.
    Взаимодействия между процессами
    Модуль multiprocessing поддерживает два основных способа взаимодей- ствия процессов: каналы и очереди. Оба способа реализованы на основе механизма передачи сообщений. Причем интерфейс очередей очень близко имитирует интерфейс очередей, которые обычно используются в многопо- точных программах.
    1   ...   40   41   42   43   44   45   46   47   ...   82


    написать администратору сайта