Саммерфилд - Программирование на Python 3 (1077331), страница 105
Текст из файла (страница 105)
А затем мы рассмотрим еще одну многопоточную программу, более сложную, чем первую, которая выполняет работу несколькими потоками и собирает воедино полученные результаты. Делегирование работы процессам В определенных ситуациях программы с необходимой функциональностью уже имеются, и требуется автоматизировать их использование. Сделать это можно с помощью модуля зцбргосввз, который предоставляет средства запуска других программ, передачи им любых параметров командной строки и в случае необходимости — возможность обмена данными с ними с помощью каналов. Один очень простой пример такой программы мы уже видели в главе 5, когда использовали функцию воЬргэсевз,са11() для очистки консоли способом, зависящим от типа платформы.
Однако эти средства могут также использоваться для создания пар программ «родитель-потомок», в которых родительская программа запускается пользователем, а она в свою очередь запускает столько экземпляров дочерней программы, сколько потребуется, причем каждой выдается отдельное задание.
Именно этот прием мы рассмотрим в данном разделе. В главе 3 мы рассматривали очень простую программу ягерцтогд.ру, которая отыскивает слово, указанное в командной строке, в файлах, имена которых перечисляются вслед за словом. В этом разделе мы разработаем более сложную версию, способную рекурсивно отыскивать файлы во вложенных подкаталогах и делегировать работу дочерним процессам, число которых зависит от наших потребностей. На экран 469 Делегирование работы процессам будет выводиться простой список имен файлов (с путями), в которых будет обнаружено искомое слово. Родительская программа находится в файле угершогс(-рру, а дочерняя— в файле угершогг)-р-сЫЫ.ру. Взаимоотношения между этими двумя программами во время работы схематически изображены на рис. 9.1.
Рис. 9.1. Родительская и дочеркие программы Основу программы угершогггрру составляет функция из!п(), которую мы рассмотрим, разделив ее на три части: Оег па!и(): сП11О = ое,раСП.)осп(ов.ратп.с!главе( Гс1е ), "дгерного-р-оп!!с.ру") орсэ, ноге, агдз = рагвв ор11опв() 111е11вс = дес 11!ев(агдв, орсэ.гесцгве) 11!ез рег ргосевв = 1еп(гс1е1сзс) // орсэ.соьпс зсагс, епс = О, гс1ез рег ргосевв ь ( !еп(гс1в1ип ) % орсэ.соопс) пцпвег = 1 Функция начинается с определения имени дочерней про- Функция граммы.
Затем сохраняются параметры командной стро- дес гс!ев() ки, полученные от пользователя. Функция рагве орт!- стр.399 опв() в своей работе использует модуль орсрагве. Она возвращает именованный кортеж Орсэ, который определяет, должна ли программа рекурсивно выполнять поиск во вложенных подкаталогах, и количество используемых процессов (значение по умолчанию 7), максимальное количество которых было выбрано нами произвольно и равно 20. Кроме того, функция возвращает искомое слово и список имен (файлов и каталогов), полученных из командной строки. Функция дет т11ев() возвращает список файлов, которые необходимо прочитать.
Получив все сведения, необходимые для решения задачи, мы определяем, сколько файлов должно быть обработано каждым из процессов. Переменные все гс и епд будут использоваться, чтобы определить часть списка г11е11вС, которая будет передаваться очередному дочернему процессу для обработки. Едва ли стоит ожидать, что число файлов Глава 9. Процессы и потоки будет кратным числу процессов, поэтому для первого дочернего процесса число файлов увеличивается на величину остатка. Переменная и серег используется исключительно для нужд отладки — чтобы при выводе результатов можно было видеть, какая строка какому процессу принадлежит. р!реа = [] вь!1е атагт < 1еп(гпе1тат): совпала = [ауа.ахасатаь)а, спт10] тт орта.парра: соввапп.аррапп(атг(пиваег)) ртре = аьаргосеаа.Рореп(соввапп, а!с!а=апаргосеаа.Р1РЕ) а!раз.аррепр(ртра) ртре.атапцвг!те(вога.епсосе( пт(8 ) «Ь"1п") Гьг Гт1епаве !и Г!1а!тат[атагт:епа]: ртре.атз!п.вг!те(Г!1апаве.епсосе(Готта") + Ь"1п") р!ре.атз!п.с1оае() пиваег «= 1 атагт, апз = апп, епа « т!1еа рег ргьсеаа Для каждой части эта гт: епЬ списка (!1е1!81 создается командная строка, состоящая из имени выполняемого файла интерпретатора Ру!)юп (которое хранится в атрибуте ауа.ехесртаЬ1е), имени файла дочерней программы, которая должна быть запущена, и параметров командной строки — в данном случае просто номер дочернего процесса, при условии, что выполнение идет в отладочном режиме.
Если файл дочерней программы содержит корректную строку «з)теЬапя» или в операционной системе настроена взаимосвязь между расширением имени файла и открывающей его программой, можно было бы сразу учесть эту информацию и не беспокоиться о включении имени выполняемого файла интерпретатора. Но мы предпочитаем приведенный подход, потому что он гарантирует, что дочерняя программа будет выполняться той же версией интерпретатора Ру$]топ, что и родительская.
Получив строку с командой, мы создаем объект арЬргосеаа. Рореп, передаем ему эту команду для выполнения (в виде списка строк) и в данном случае задаем возможность записывать данные в поток стандартного ввода процесса. (Точно так же возможно читать данные из потока стандартного вывода, определив похожий именованный аргумент.) После этого мы записываем в поток стандартного ввода процесса искомое слово, сопровождая его символом перевода строки, а затем все имена файлов из соответствующей части списка Гт1е1!ат. Модуль арЬ- ргосеаа читает и записывает байты, а не строки, поэтому мы должны кодировать строки при записи (и декодировать байты при чтении), используя соответствующую кодировку, и в данном случае мы выбрали ()ТУ-8. Закончив передачу списка файлов дочернему процессу, мы закрываем поток стандартного ввода и идем дальше.
Нет никакой необходимости сохранять ссылку на каждый дочерний процесс (на каждой новой итерации цикла в переменную р[ре будет за- Делегирование работы процессам 471 писываться новая ссылка на объект аоЬргосеза. Рареп), потому что каждый процесс выполняется независимо, но мы сохраняем их в списке, чтобы иметь возможность прерывать их работу. Кроме того, мы не выполняем сбор информации, которая выводится дочерними процессами; вместо этого мы позволяем им выполнять вывод результатов на консоль. Это означает, что результаты, выводимые несколькими процессами, могут выводиться вперемешку.
(В упражнениях вам будет предоставлена возможность предотвратить такое перемешивание.) нпт1е р!реа: р[ра = р[реа.рор() р1ре. на!1() После запуска всех дочерних процессов мы ожидаем, пока каждый из них завершит свою работу. В УХ1Х-системах это гарантирует такую, может быть, не очень существенную особенность: после того как все процессы завершатся, управление передается командной строке (в противном случае нам было бы необходимо нажать клавишу Ел!ег после завершения всех процессов). Другое преимущество такого подхода состоит в следующем: если работа программы прерывается (например, нажатием комбинации клавиш С1г[+С), то все дочерние процессы, которые к этому моменту продолжают работу, будут прерваны и завершены с исключением Кеузоаг01п!еггорт, которое нельзя перехватить.
В противном случае главная программа завершится (потеряв возможность прерывать работу дочерних процессов) и дочерние процессы продолжат работу (пока их не остановит команда К!11 или менеджер задач). Ниже приводится полный программный код (за исключением комментариев и инструкций!врог!) дочерней программы дгергоогдр-сЬ(Ыру, разбитый на две части: В(ОСК 317Е = 8000 повЬаг = "(0): ".Гогват(ауа.агдн[1]) (Г 1ап(ауа.агдч) == 2 е1ае ного = ауа.ато!и. геао11па().
гатг1р() Программа начинается с того, что запоминает свой номер или пустую строку, если она выполняется не в отладочном режиме. После этого она читает первую строку, содержащую искомое слово. Эта и вся последующая информация читается как строки. Гог (11епаве [п ауа,атс[п: (11епава = г!1апава,гатт(р() ргао1ооа = "" тгу. н[ть орел((11апава, "гь") аа гь: н(Н1а Тгоа: сиггепт = Гп.гаао(В[ОСК 817Е) 11 пот соггапт: Ьгеап Глава 9. Процессы и потоки счггап! = соггапт.засосе("от(8", "!слога") гр (иогз !и соггепт ог иогс !и ргечтооа[-1еп(иогс):] + соггаж[:1ап(иого)!): рг!пт("(0)(1)".(огиат(поиьаг, гт1апаие)) ЬгааК !т 1еп(соггап!) '.= 8ЕОСК 81ЕЕ; ЬгааК ргачтооа = соггапт ехсерт Епч!гопиептЕггог аа егг: ргтпт("(0)(1)".(огиат(поиЬег, агг)) Все строки, кроме первой, являются именами файлов (с путями).
Для каждого имени программа открывает соответствующий файл, читает его содержимое и выводит имя файла, если в нем обнаруживается искомое слово. Вполне возможно, что некоторые файлы могут иметь очень большой размер, что может привести к проблеме нехватки памяти, особенно, когда параллельно выполняются 20 дочерних процессов и все они читают большие файлы.
Мы ликвидируем эту проблему, читая каждый файл блоками, всякий раз сохраняя предыдущий прочитанный блок, чтобы гарантировать, что учтен случай попадания единственного вхождения искомого слова на границу двух блоков. Еще одно преимущество чтения файлов блоками состоит в том, что если искомое слово находится в начале файла, мы можем завершить его чтение, не читая весь файл целиком, поскольку нам достаточно знать, что слово присутствует в файле, и не важно, в каком месте внутри файла оно встречается. а Чтение файлов выполняется в двоичном режиме, поэтоКодировки сииаопоа му мы должны преобразовывать каждый блок в строку, стр.118 прежде чем можно будет выполнять поиск, так как искомое слово является строкой. Мы исходим из предположения, что во всех файлах содержится текст в кодировке 1)ТУ-8, но в некоторых случаях зто предположение может оказаться неверным.
Более сложная версия программы могла бы сначала пытаться определить фактическую кодировку, затем закрывать файл и повторно открывать его уже с корректной кодировкой. Как уже отмечалось в главе 2, существует по меньшей мере два пакета автоматического определения кодировки файлов, которые доступны в каталоге пакетов Ру!)топ Рас]сане 1пт[ех, рур(ру!)топ.огу]рур!. (Может показаться заманчивым декодировать искомое слово в объект Ьутеа и сравнивать объект Ьутеа с объектом Ьутеа, но такой прием не дает полной надежности, так как некоторые символы могут иметь более одного допустимого представления в кодировке УТР-8.) Делегирование работы потокам выполнения 473 Модуль ззЬргосевз предлагает гораздо более широкие возможности, чем было использовано здесь, включая эквиваленты обратным апострофам и конвейерам командной оболочки, а также функциям ов.