Саммерфилд - Программирование на Python 3 (1077331), страница 106
Текст из файла (страница 106)
вузгез() и функциям порождения дочерних процессов. В следующем разделе мы рассмотрим многопоточную версию программы ягергаогг1р.ру, благодаря чему мы сможем сравнить ее с версией, которая запускает дочерние процессы. Мы также увидим более сложную многопоточную программу, которая распределяет работу между потоками выполнения и собирает результаты воедино, что дает больший контроль над тем, как они будут выводиться. Делегирование работы потокам выполнения Создание двух или более потоков выполнения в языке Ру$)гоп производится достаточно просто.
Сложности появляются, когда возникает необходимость использовать одни и те же данные в разных потоках. Представьте, что два потока совместно пользуются одним и тем же списком. Один поток может приступить к выполнению итераций по списку, используя инструкцию аког х 1л Г., а второй поток аэто же время удаляет несколько элементов где-нибудь в середине списка. В лучшем случае это будет приводить к неожиданному аварийному завершению программы, а в худшем — к получению неверных результатов.
Одно из типичных решений этой проблемы заключается в использовании механизма блокировки. Например, один поток может сначала получить блокировку и только потом начинать итерации по списку— в это время любой другой поток будет заблокирован, ожидая снятия блокировки. В действительности все не так просто. Связь между блокировкой и данными, которые она запирает, существует только в нашем сознании. Если один поток уже получил блокировку и ее попытается запросить второй поток, он окажется заблокированным, пока первый поток не освободит блокировку. Реализуя доступ к данным через получение блокировки, мы можем гарантировать, что в каждый конкретный момент доступ к данным будет иметь только один поток, хотя сама защита носит косвенный характер.
Одна из проблем, связанных с блокировками, заключается в том, что существует риск появления ситуации взаимоблокировки. Предположим, что поток ДГ11 получает блокировку А, получая доступ к элементу совместно используемых данных а, а затем пытается получить блокировку В, чтобы получить возможность доступа к элементу совместно используемых данных Ь, но не может этого сделать, потому что в это же время поток ЛФ2 уже имеет блокировку В для доступа к элементу совместно используемых данных Ь и в свою очередь пытается получить блокировку А, чтобы получить доступ к данным а.
То есть поток М1, имея блокировку А, пытается получить блокировку В, в то время как поток Ля 2, имея блокировку В, пытается получить блокировку А. Глава 9. Процессы и потоки менты с наименьшим значением приоритета обрабатываются первыми. Для всех очередей можно определить максимальный размер — если будет достигнут максимальный размер, очередь блокирует все последующие попытки добавить элементы, пока из нее не будут удалены существующие элементы. Мы рассмотрим программу агершогг)-1.ру, разделив ее на три части, и начнем с полного определения функции ва1п( ): Оет вагп(): орта, вота, агда = рагае оп(топе() (11е11ат = дет 11)ее(агре, орта.гесигае) вогк пиесе = пиесе.сиеие() тог т тп гапде(орта.соипт): пивьег = "(0): ".(огват(т в 1) тт орта.оеьид е1ае вогкег = 'погкег(вогК Пиеие, вега, пивЬег) вогКег,саевоп = Тгие иогкеы атагт() тог (11епаве 1п (11е11ат: вогк оиаие рит((11епаве) вогК Пиесе.)о1п() Получение параметров командной строки и списка имен файлов выполняется точно так же, как и раньше.
После сбора всей необходимой информации создается очередь типа Сивое. ()иеие и затем выполняются циклы по числу создаваемых потоков (по умолчанию 7). Для каждого потока подготавливается строка, содержащая порядковый номер потока при работе в отладочном режиме (пустая строка — в противном случае), и затем создается экземпляр класса ИогКег (подкласс класса то гвалт(пд, ТЬ геао) — описание его свойства иаевоп приводится чуть ниже. После этого поток запускается на выполнение. В этот момент времени для него еще нет работы, поскольку очередь заданий еще пуста, поэтому поток тут же окажется заблокированным на попытке получить задание из очереди. После того как будут созданы и запущены все потоки, функция выполняет итерации по всем именам файлов, добавляя каждый из них в очередь заданий.
Как только в очередь будет добавлен первый файл, один из потоков сможет получить его и начать поиск слова, и это будет происходить, пока все потоки не получат по файлу для выполнения своей работы. Как только поток завершит обработку файла, он тут же получит из очереди следующий файл, и так до тех пор, пока не будут обработаны все файлы. Обратите внимание, что такая организация распределения работы отличается от принятой в программе иге)ошогс(.р)ту, где каждому дочернему процессу выделялся фрагмент списка, после чего дочерние процессы последовательно обрабатывают файлы в своих списках. В случаях, подобных этому, применение потоков выполнения дает более высокую производительность. Например, если первые пять файлов в списке имеют очень большой размер, а остальные файлы очень маленькие, то Делегирование работы потокам выполнения 477 каждый большой файл будет обрабатываться отдельным потоком, так как каждый поток извлекает из очереди только одно задание за раз, что приведет к более равномерному распределению нагрузки.
В программе дегрогогд-р.ру. напротив, все большие файлы достанутся первому дочернему процессу, а остальным процессам — маленькие файлы, поэтому на долю первого процесса может выпасть значительный объем работы, в то время как остальные процессы могут быстро завершиться, сделав, по сути, не так много. Программа будет продолжать работу, пока имеется хотя бы один запущенный поток выполнения. Это порождает определенную проблему, так как с технической точки зрения даже после выполнения всех заданий потоки все равно считаются выполняющимися.
Решение этой проблемы состоит в том„чтобы превратить потоки в демонов. Это позволит программе завершиться, как только в ней не останется ни одного работающего потока, не являющегося демоном. Главный поток не является демоном, поэтому, как только главный поток завершится, программа завершит работу каждого потока-демона и завершится сама. Конечно, теперь может возникнуть противоположная проблема — после создания и запуска всех потоков нам необходимо гарантировать, что главный поток программы не завершится, пока не будет выполнена вся работа.
Добиться этого можно вызовом метода соеое. Ооеое. )сап() — этот метод блокирует вызывающий его поток, пока очередь не опустеет. Ниже приводится начало определения класса Ио гКе г: с1ааа исгкаг(тпгеаб1пп.тпгааб): бег тп11 (ае1г, всгк поеое, всгб, повьаг); аорег().
тптт () ае1т.всгк поеое = всгк соеое ае1(.всгб = всгб ае!Г.повЬег = повЬаг бат гоп(аа1(): вю1а Тгое: тгу: Гт1апаве = аа1(.всгК Соеое.эат() ае1(.ргссааа((11апаве) Г1па11у: аа1т.всгк Спаса.тааК бове() В методе 1п11 () в обязательном порядке должен вызываться метод 1птт () базового класса. Аргумент всгк Соеое — это та самая очередь заданий соеое, Ооеое, которая совместно используется всеми потоками.
Метод гоп() выполняет бесконечный цикл. Это типичная организация работы потока-демона, и в этом определенно есть смысл, так как заранее неизвестно, сколько файлов предстоит обработать потоку. В каждой итерации вызывается метод Соеое. Ооеое.
Ое1( ), чтобы получить имя следующего файла для обработки. Этот вызов заблокирует выполнение потока, если очередь окажется пуста, и нам не требуется преду- 478 Глава 9. Процессы и потоки ематривать свои блокировки, так как все необходимые действия выполняютея классом цсеое.
диезе автоматически. Получив файл, поток обрабатывает его, после чего необходимо сообщить очереди, что данное задание было выполнено, вызовом метода цзеце. Овесе. тзвк боле(), что совершенно необходимо для обеспечения корректной работы метода диезе. Оиеое,)с!и(). Мы не показали реализацию функции ргссевв(), потому что, кроме строки с инструкцией бе1, ее программный код, начиная со строки р геч!сов = "" и до конца, остался тем же, что и в программе угершогт(.р. сИ(т).ру (на стр.
471). Последнее, что хотелось бы отметить: в состав примеров к книге включена программа угершогд-лт.ру, практически идентичная программе угершогт(-1 ру, рассматривавшейся здесь, но в ней вместо модуля тлгеаб!пд используется модуль зи11!ргсеевв!пд. Ее программный код имеет ровно три отличия: первое — она импортирует модуль сз11!ргсеевв!пд вместо модулей цсеее и тл геаб!пд; второе — класс Исгиег наследует класс нс11!ргосевв!пд. Ргосевв, а не тпгеабтпд,тпгеаб и третье — очередь заданий в ней имеет тип нс111ргсеевв!пд,,)е!пзв)едсесе, а не диезе. Овесе. Модулыц11!ргсеевв!пд реализует функциональность, напоминающую потоки, используя механизм ветвления в системах, поддерживающих его (УХ1Х), и порождает дочерние процессы в системах, которые не поддерживают ветвление (МГ!пт(отвв), благодаря чему механизм блокировок оказывается необходимым не всегда, а сами процессы будут выполняться на всех ядрах процессора, доступных операционной системе.