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

  • 4.5.1. Класс Thread

  • Таблица 4.3.

  • Создание экземпляра Thread с передачей функции

  • Пример 4.4. Использование модуля threading (mtsleepC.py)

  • Создание экземпляра Thread и передача вызываемого экземпляра класса

  • Пример 4.5. Использование вызываемых классов ( mtsleepD.py)

  • Подкласс Thread и создание экземпляра подкласса

  • Пример 4.6. Создание подкласса Thread (mtsleepE.py)

  • Пример 4.7. Подкласс MyThread потока (myThread.py)

  • Многопоточное программированиеВ этой главе


    Скачать 0.74 Mb.
    НазваниеМногопоточное программированиеВ этой главе
    Дата24.04.2023
    Размер0.74 Mb.
    Формат файлаpdf
    Имя файлаpart.pdf
    ТипДокументы
    #1086381
    страница3 из 8
    1   2   3   4   5   6   7   8
    Потоки, функционирующие в качестве демонов
    Еще одна причина, по которой следует избегать использование модуля thread, состо- ит в том, что он не поддерживает принцип организации работы программы на основе демонов (потоков, работающих в фоновом режиме). Модуль thread действует так, что после выхода из основного потока все дочерние потоки уничтожаются, без учета того, должны ли они продолжить выполнение определенной работы. Если это нежелательно, то можно организовать функционирование потоков в качестве демонов.
    Поддержка демонов предусмотрена в модуле threading. Ниже описано, как они функ- ционируют. Обычно демон применяется в качестве сервера, который ожидает поступле- ния клиентских запросов, подлежащих выполнению. Если нет никакой работы, посту- пившей от клиентов, которая должна быть сделана, то демон простаивает. Для потока может быть установлен флаг, указывающий, что этот поток может выполнять роль демо- на. Такое указание равносильно обозначению родительского потока как не требующего после своего завершения, чтобы был завершен дочерний поток. Как было описано в гла- ве 2, потоки сервера функционируют в виде бесконечных циклов и в обычных ситуациях не завершают свою работу.
    Дочерние потоки могут быть также обозначены флагами как демоны, если в основном потоке могут складываться условия готовности к выходу, но нет необходимости ожидать завершения работы дочерних потоков, чтобы выйти из основной программы. Значение true указывает, что дочерний поток не приносит результатов, от которых зависит воз- можность завершения всей программы, и в основном рассматривается как указание, что единственным назначением потока является ожидание запросов от клиентов и их обслу- живание.
    Чтобы обозначить поток как выполняющий функции демона, необходимо применить оператор присваивания thread.daemon = True, прежде чем запустить этот поток.
    (Устаревший способ осуществления этой задачи, заключавшийся в вызове операто- ра thread.setDaemon(True), теперь запрещен.) То же является справедливым в от- ношении проверки того, выполняет ли поток функции демона; достаточно проверить значение соответствующей переменной (а не вызывать функцию thread.isDaemon()).
    Новый дочерний поток наследует свой флаг, обозначающий его в качестве демона, от родительского потока. Вся программа Python (рассматриваемая как основной поток) продолжает функционировать до тех пор, пока не произойдет выход из всех потоков, не обозначенных как демоны, иными словами, до тех пор, пока остаются активными какие- либо потоки, не действующие как демоны.
    4.5.1. Класс
    Thread
    В программе с многопоточной организацией главным инструментом является класс Thread модуля threading. Модуль threading поддерживает целый ряд функ- ций, отсутствующих в модуле thread. В табл. 4.3 представлен список атрибутов и ме- тодов модуля threading.
    06_ch04.indd 195 22.01.2015 22:00:40

    Глава 4

    Многопоточное программирование
    196
    Таблица 4.3. Атрибуты и методы объекта класса Thread
    Атрибут
    Описание
    Атрибуты данных объекта потока
    name
    Имя потока
    Ident
    Идентификатор потока
    Daemon
    Булев флаг, указывающий, выполняет ли поток функции демона
    Методы объекта потока
    __init__(group=None,
    target=None, name=None,
    args=(), kwargs={},
    verbose=None,daemon=None
    )
    c
    Порождение объекта Thread с использованием целевого параме- тра
    callable и набора параметров args или kwargs. Может быть также передан параметр
    name или group, но обработка по- следнего не реализована. Принимается также флаг
    verbose. Лю- бое ненулевое значение
    daemon задает атрибут/флаг thread.
    daemon
    start()
    Запуск выполнения потока run()
    Метод, определяющий функционирование потока (обычно пере- крывается разработчиком приложения в подклассе)
    join(timeout=None)
    Приостановка до завершения запущенного потока; блокировка, если не задан параметр
    timeout (в секундах)
    getName()
    a
    Возвращаемое имя потока setName(name)
    a
    Заданное имя потока isAlive/is_alive()
    b
    Булев флаг, указывающий, продолжает ли поток работать isDaemon()
    c
    Возвращает
    True, если поток выполняет функции демона, в про- тивном случае возвращает
    False setDaemon(daemonic)
    c
    Задание флага работы в режиме демона равным указанному бу- леву значению
    daemonic (вызов должен осуществляться перед выполнением функции start() для потока)
    a
    Обозначается как устаревший путем задания (или получения) атрибута thread.name или передачи его во время порождения экземпляра.
    b
    Имена в так называемом ВерблюжьемСтиле (CamelCase) рассматриваются как устарев- шие и заменяются, начиная с версии Python 2.6.
    c
    Метод is/setDaemon() обозначается как устаревший путем задания атрибута thread.
    daemon; значение thread.daemon может быть также задано во время порождения экзем- пляра путем указания необязательного значения для демона; новое в версии Python 3.3.
    Предусмотрен целый ряд способов, с помощью которых могут создаваться потоки на основе класса Thread. В данной главе рассматриваются три из этих способов, ко- торые мало отличаются друг от друга. Программист может выбрать способ, который является для него наиболее удобным, не говоря уже о том, что выбранный способ должен быть наиболее подходящим с точки зрения приложения и масштабирования в будущем (предпочтительным является последний из приведенных способов).

    Создание экземпляра Thread с передачей функции.

    Создание экземпляра Thread и передача вызываемого экземпляра класса.

    Формирование подкласса Thread и создание экземпляра подкласса.
    06_ch04.indd 196 22.01.2015 22:00:41

    197 4.5. Модуль threading
    Как правило, программисты выбирают первый или третий вариант. Последний становится предпочтительным, если требуется создать в большей степени объек- тно-ориентированный интерфейс, а первый — в противном случае. Второй вариант, откровенно говоря, является немного более громоздким, и, как показывает практика, его применение приводит к созданию программ, более сложных для восприятия.
    Создание экземпляра
    Thread с передачей функции
    В первом примере будет лишь создан экземпляр Thread с передачей функции
    (с ее параметрами) в форме, аналогичной предыдущим примерам. Именно эта функ- ция должна быть выполнена после передачи потоку указания, что он должен начать выполнение. Взяв за основу сценарий mtsleepB.py из примера 4.3 и внеся в него кор- ректировки, необходимые для использования объектов Thread, получим сценарий mtsleepC.py, как показано в примере 4.4.
    Пример 4.4. Использование модуля
    threading (mtsleepC.py)
    В классе Thread из модуля threading предусмотрен метод join(), который по- зволяет обеспечить ожидание в основном потоке завершения текущего потока.
    1 #!/usr/bin/env python
    2 3 import threading
    4 from time import sleep, ctime
    5 6 loops = [4,2]
    7 8 def loop(nloop, nsec):
    9 print 'start loop', nloop, 'at:', ctime()
    10 sleep(nsec)
    11 print 'loop', nloop, 'done at:', ctime()
    12 13 def main():
    14 print 'starting at:', ctime()
    15 threads = []
    16 nloops = range(len(loops))
    17 18 for i in nloops:
    19 t = threading.Thread(target=loop,
    20 args=(i, loops[i]))
    21 threads.append(t)
    22 23 for i in nloops: # запуск потоков
    24 threads[i].start()
    25 26 for i in nloops: # ожидание завершения
    27 threads[i].join() # всех потоков
    28 29 print 'all DONE at:', ctime()
    30 31 if __name__ == '__main__':
    32 main()
    После выполнения сценария, приведенного в примере 4.4, формируется пример- но такой же вывод, как и при вызове предыдущих сценариев:
    06_ch04.indd 197 22.01.2015 22:00:41

    Глава 4

    Многопоточное программирование
    198
    $ mtsleepC.py starting at: Sun Aug 13 18:16:38 2006 start loop 0 at: Sun Aug 13 18:16:38 2006 start loop 1 at: Sun Aug 13 18:16:38 2006 loop 1 done at: Sun Aug 13 18:16:40 2006 loop 0 done at: Sun Aug 13 18:16:42 2006 all DONE at: Sun Aug 13 18:16:42 2006
    Так что же фактически изменилось? Удалось избавиться от блокировок, которые приходилось реализовывать при использовании модуля thread. Вместо этого соз- дается ряд объектов Thread. После создания экземпляра каждого объекта Thread остается лишь передать функцию (target) и параметры (args) и получить взамен экземпляр Thread. Наибольшее различие между созданием экземпляра Thread (пу- тем вызова Thread()) и вызовом thread.start_new_thread() состоит в том, что в первом случае запуск нового потока не происходит немедленно. Это удобно с точки зрения синхронизации, особенно если не требуется, чтобы потоки запускались сразу после их создания.
    Иными словами, появляется возможность почти одновременно запустить все потоки по окончании их распределения, но не раньше, для чего остается лишь вы- звать метод start() каждого потока. Кроме того, отпадает необходимость зани- маться управлением целым рядом блокировок (выделением, захватом, освобожде- нием, проверкой состояния блокировки и т.д.), поскольку достаточно лишь вызвать метод join() для каждого потока. Метод join() обеспечивает переход в состояние ожидания до завершения работы потока или до истечения тайм-аута, если он пред- усмотрен. Использование метода join() открывает путь к созданию гораздо более наглядных программ по сравнению с применением бесконечного цикла, в котором происходит ожидание освобождения блокировок (такие блокировки иногда имену- ются спин-блокировками, или “крутящимися” блокировками, именно по той причине, что применяются в бесконечном цикле).
    Еще одной важной отличительной особенностью метода join() является то, что он вообще не требует вызова. После запуска потока его выполнение происходит до завершения переданной ему функции, после чего осуществляется выход из потока.
    Если в основном потоке должны быть выполнены какие-то другие действия, кроме ожидания завершения потоков (такие как дополнительная обработка или ожидание новых клиентских запросов), организовать это совсем несложно. Метод join() ста- новится удобным, только если требуется обеспечить ожидание завершения потока.
    Создание экземпляра
    Thread и передача
    вызываемого экземпляра класса
    Подход, аналогичный передаче функции при создании потока, состоит в приме- нении вызываемого класса и передаче его экземпляра на выполнение; в этом состоит в большей степени объектно-ориентированный способ многопоточного программи- рования. Такой вызываемый класс воплощает в себе среду выполнения, а это откры- вает намного больше возможностей по сравнению с применением функции или выбором из ряда функций. Теперь в руках у программиста оказывается вся мощь объекта класса, а не просто единственной функции или даже ряда функций, опреде- ляемого списком или кортежем.
    06_ch04.indd 198 22.01.2015 22:00:41

    199 4.5. Модуль threading
    После введения в код нового класса ThreadFunc и внесения других небольших из- менений в сценарий mtsleepC.py был создан сценарий mtsleepD.py, показанный в примере 4.5.
    Пример 4.5. Использование вызываемых классов (
    mtsleepD.py)
    В этом примере передается вызываемый класс (экземпляр), в отличие от отдель- ной функции. Такой подход является в большей степени объектно-ориентированным по сравнению с применяемым в mtsleepC.py.
    1 #!/usr/bin/env python
    2 3 import threading
    4 from time import sleep, ctime
    5 6 loops = [4,2]
    7 8 class ThreadFunc(object):
    9 10 def __init__(self, func, args, name=''):
    11 self.name = name
    12 self.func = func
    13 self.args = args
    14 15 def __call__(self):
    16 self.func(*self.args)
    17 18 def loop(nloop, nsec):
    19 print 'start loop', nloop, 'at:', ctime()
    20 sleep(nsec)
    21 print 'loop', nloop, 'done at:', ctime()
    22 23 def main():
    24 print 'starting at:', ctime()
    25 threads = []
    26 nloops = range(len(loops))
    27 28 for i in nloops: # создание всех потоков
    29 t = threading.Thread(
    30 target=ThreadFunc(loop, (i, loops[i]),
    31 loop.__name__))
    32 threads.append(t)
    33 34 for i in nloops: # запуск всех потоков
    35 threads[i].start()
    36 37 for i in nloops: # ожидание завершения
    38 threads[i].join()
    39 40 print 'all DONE at:', ctime()
    41 42 if __name__ == '__main__':
    43 main()
    После вызова на выполнение сценария mtsleepD.py формируется ожидаемый вывод:
    06_ch04.indd 199 22.01.2015 22:00:41

    Глава 4

    Многопоточное программирование
    200
    $ mtsleepD.py starting at: Sun Aug 13 18:49:17 2006 start loop 0 at: Sun Aug 13 18:49:17 2006 start loop 1 at: Sun Aug 13 18:49:17 2006 loop 1 done at: Sun Aug 13 18:49:19 2006 loop 0 done at: Sun Aug 13 18:49:21 2006 all DONE at: Sun Aug 13 18:49:21 2006
    Так что же изменилось на этот раз? Произошло добавление класса ThreadFunc и внесены небольшие изменения в процедуру создания экземпляра объекта Thread, в которой также порождается ThreadFunc, новый вызываемый класс. Фактически в данном примере применяется процедура создания не одного, а двух экземпляров.
    Рассмотрим класс ThreadFunc более подробно.
    Этот класс должен быть достаточно общим, для того чтобы его можно было ис- пользовать не только с функцией loop(), но и с другими функциями, поэтому была добавлена некоторая новая инфраструктура, которая обеспечивает хранение этим классом параметров для функции, самой функции, а также строки с именем функ- ции. Конструктор __init__() лишь задает все необходимые значения.
    При вызове в коде Thread объекта ThreadFunc в связи с созданием нового пото- ка вызывается специальный метод __call__(). Необходимый набор параметров уже задан, поэтому его не обязвтельно передавать конструктору Thread() и можно вызы- вать функцию непосредственно.
    Подкласс
    Thread и создание экземпляра подкласса
    В последнем вводном примере рассмотрим создание подкласса Thread(). Как оказалось, применяемая при этом последовательность действий весьма напоминает то, что происходит при создании вызываемого класса, как в предыдущем примере.
    Код создания подкласса является немного более легким для восприятия, если дело касается создания потоков (строки 29-30). Код сценария mtsleepE.py представлен в примере 4.6, затем показан вывод, полученный в результате выполнения этого сцена- рия, а сравнение сценариев mtsleepE.py и mtsleepD.py оставлено в качестве упраж- нения для читателя.
    Пример 4.6. Создание подкласса
    Thread (mtsleepE.py)
    Вместо создания экземпляра класса Thread создается его подкласс. Благодаря это- му открываются более широкие возможности настройки объектов многопоточной поддержки и упрощается осуществление действий по созданию потока.
    1 #!/usr/bin/env python
    2 3 import threading
    4 from time import sleep, ctime
    5 6 loops = (4, 2)
    7 8 class MyThread(threading.Thread):
    9 def __init__(self, func, args, name=''):
    10 threading.Thread.__init__(self)
    11 self.name = name
    12 self.func = func
    13 self.args = args
    06_ch04.indd 200 22.01.2015 22:00:42

    201 4.5. Модуль threading
    14 15 def run(self):
    16 self.func(*self.args)
    17 18 def loop(nloop, nsec):
    19 print 'start loop', nloop, 'at:', ctime()
    20 sleep(nsec)
    21 print 'loop', nloop, 'done at:', ctime()
    22 23 def main():
    24 print 'starting at:', ctime()
    25 threads = []
    26 nloops = range(len(loops))
    27 28 for i in nloops:
    29 t = MyThread(loop, (i, loops[i]),
    30 loop.__name__)
    31 threads.append(t)
    32 33 for i in nloops:
    34 threads[i].start()
    35 36 for i in nloops:
    37 threads[i].join()
    38 39 print 'all DONE at:', ctime()'
    40 41 if __name__ == '__main__':
    42 main()
    Ниже приведен вывод сценария mtsleepE.py. В данном случае полученные ре- зультаты вполне соответствуют ожиданию:
    $ mtsleepE.py starting at: Sun Aug 13 19:14:26 2006 start loop 0 at: Sun Aug 13 19:14:26 2006 start loop 1 at: Sun Aug 13 19:14:26 2006 loop 1 done at: Sun Aug 13 19:14:28 2006 loop 0 done at: Sun Aug 13 19:14:30 2006 all DONE at: Sun Aug 13 19:14:30 2006
    Сравнивая исходный код модулей mtsleep4 и mtsleep5, необходимо подчеркнуть наиболее значительные отличия: во-первых, в конструкторе подкласса MyThread приходится вначале вызывать конструктор базового класса (строка 9), и, во-вторых, применявшийся ранее специальный метод __call__() должен получить в подклассе имя run().
    После этого дополним класс MyThread некоторыми средствами формирования диагностического вывода и сохраним его в отдельном модуле myThread (как показано в примере 4.7). В следующих примерах этот класс будет применяться для импорта.
    Вместо того чтобы просто вызывать применяемые функции, сохраним результат в атрибуте экземпляра self.res и создадим новый метод для получения этого значе- ния, getResult().
    06_ch04.indd 201 22.01.2015 22:00:42

    Глава 4

    Многопоточное программирование
    202
    Пример 4.7. Подкласс
    MyThread потока (myThread.py)
    Для того чтобы повысить общность подкласса Thread из сценария mtsleepE.py, переместим этот подкласс в отдельный модуль и добавим метод getResult() для вызова функций, которые формируют возвращаемые значения.
    1 #!/usr/bin/env python
    2 3 import threading
    4 from time import ctime
    5 6 class MyThread(threading.Thread):
    7 def __init__(self, func, args, name=''):
    8 threading.Thread.__init__(self)
    9 self.name = name
    10 self.func = func
    11 self.args = args
    12 13 def getResult(self):
    14 return self.res
    15 16 def run(self):
    17 print 'starting', self.name, 'at:', \
    18 ctime()
    19 self.res = self.func(*self.args)
    20
    1   2   3   4   5   6   7   8


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