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

  • 4.7.2. Примитивы синхронизации

  • 4.7.3. Пример применения блокировки

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


    Скачать 0.74 Mb.
    НазваниеМногопоточное программированиеВ этой главе
    Дата24.04.2023
    Размер0.74 Mb.
    Формат файлаpdf
    Имя файлаpart.pdf
    ТипДокументы
    #1086381
    страница5 из 8
    1   2   3   4   5   6   7   8
    Перенос приложения в версию Python 3
    Теперь перейдем к рассмотрению еще одного варианта данного сценария, который предназначен для работы с версией Python 3. С этой темой необ- ходимо ознакомиться, изучая пути переноса проектов и приложений из те- кущей версии интерпретатора Python в последующую версию. К счастью, эту работу не требуется выполнять вручную, поскольку уже предусмотрены необходимые инструменты, одним из которых является инструмент 2to3.
    Вообще говоря, предусмотрены два способа его использования:
    $ 2to3 foo.py # в выводе показаны только различия
    $ 2to3 -w foo.py # переопределяет с помощью кода версии 3.x
    В первой команде инструмент 2to3 лишь отображает различия между исходным сценарием в версии 2.x и сформированным с его помощью эквивалентом для версии
    3.x. Флаг -w служит для инструмента 2to3 указанием, что исходный сценарий должен быть перезаписан вновь полученным сценарием для версии 3.x, а сценарий для вер- сии 2.x переименован в foo.py.bak.
    Вызовем на выполнение инструмент 2to3 применительно к файлу сценария bookrank.py с перезаписью существующего файла. Предусмотрен не только вывод различий; сохраняется также новая версия, как уже было сказано:
    $ 2to3 -w bookrank.py
    RefactoringTool: Skipping implicit fixer: buffer
    RefactoringTool: Skipping implicit fixer: idioms
    RefactoringTool: Skipping implicit fixer: set_literal
    RefactoringTool: Skipping implicit fixer: ws_comma
    --- bookrank.py (original)
    +++ bookrank.py (refactored)
    @@ -4,7 +4,7 @@ from re import compile from threading import Thread from time import ctime
    -from urllib2 import urlopen as uopen
    +from urllib.request import urlopen as uopen
    REGEX = compile('#([\d,]+) in Books ')
    AMZN = 'http://amazon.com/dp/'
    @@ -21,17 +21,17 @@ return REGEX.findall(data)[0] def _showRanking(isbn):
    - print '- %r ranked %s' % (
    - ISBNs[isbn], getRanking(isbn))
    + print('- %r ranked %s' % (
    + ISBNs[isbn], getRanking(isbn))) def _main():
    - print 'At', ctime(), 'on Amazon...'
    06_ch04.indd 210 22.01.2015 22:00:45

    211 4.7. Практическое применение многопоточной обработки
    + print('At', ctime(), 'on Amazon...') for isbn in ISBNs:
    Thread(target=_showRanking, args=(isbn,)).start()#_showRanking(isbn)
    @register def _atexit():
    - print 'all DONE at:', ctime()
    + print('all DONE at:', ctime()) if __name__ == '__main__':
    _main()
    RefactoringTool: Files that were modified:
    RefactoringTool: bookrank.py
    Следующий шаг читатели могут рассматривать как необязательный. Достаточно лишь отметить, что в нем рассматриваемые файлы были переименованы в bookrank.
    py и bookrank3.py с использованием команд POSIX (пользователи компьютеров с операционной системой Windows должны использовать команду ren):
    $ mv bookrank.py bookrank3.py
    $ mv bookrank.py.bak bookrank.py
    Разумеется, было бы желательно, чтобы преобразование сценария для использо- вания в новой версии интерпретатора прошло идеально, чтобы не пришлось ни о чем заботиться, приступая к работе со сценарием нового поколения. Однако в дан- ном случае произошло нечто непредвиденное и в каждом потоке возникает исключе- ние (приведенный вывод относится только к одному потоку; нет смысла показывать результаты для других потоков, поскольку они являются такими же):
    $ python3 bookrank3.py
    Exception in thread Thread-1:
    Traceback (most recent call last):
    File "/Library/Frameworks/Python.framework/Versions/
    3.2/lib/python3.2/threading.py", line 736, in
    _bootstrap_inner self.run()
    File "/Library/Frameworks/Python.framework/Versions/
    3.2/lib/python3.2/threading.py", line 689, in run self._target(*self._args, **self._kwargs)
    File "bookrank3.py", line 25, in _showRanking
    ISBNs[isbn], getRanking(isbn)))
    File "bookrank3.py", line 21, in getRanking return REGEX.findall(data)[0]
    TypeError: can't use a string pattern on a bytes-like object
    :
    Что же случилось? По-видимому, проблема заключается в том, что регулярное выражение представлено в виде строки Юникода, тогда как данные, полученные с помощью метода read() файлового объекта (возвращенного функцией urlopen()), имеют вид строки ASCII/bytes. Чтобы исправить эту ошибку, откомпилируем вме- сто текстовой строки объект bytes. Для этого внесем изменения в строку 9, чтобы в методе re.compile() производилась компиляция строки bytes (добавим строку bytes). Для этого добавим обозначение b строки bytes непосредственно перед от- крывающей кавычкой следующим образом:
    06_ch04.indd 211 22.01.2015 22:00:45

    Глава 4

    Многопоточное программирование
    212
    REGEX = compile(b'#([\d,]+) in Books ')
    Now let's try it again:
    $ python3 bookrank3.py
    At Sun Apr 3 00:45:46 2011 on Amazon...
    - 'Core Python Programming' ranked b'108,796'
    - 'Python Web Development with Django' ranked b'268,660'
    - 'Python Fundamentals' ranked b'969,149' all DONE at: Sun Apr 3 00:45:49 2011
    Опять что-то не так! Что же случилось теперь? Безусловно, результат стал немного лучше (нет ошибок), но выглядит странно. В данных ранжирования, полученных с помощью регулярных выражений, после передачи в функцию str() отображаются символы b и кавычки. Для устранения этого недостатка первым побуждением может стать попытка применить операцию получения среза строки, которая также выгля- дит довольно неуклюже:
    >>> x = b'xxx'
    >>> repr(x)
    "b'xxx'"
    >>> str(x)
    "b'xxx'"
    >>> str(x)[2:-1]
    'xxx'
    Тем не менее более подходящий вариант состоит в применении операции преоб- разования данных в действительное значение (строка в Юникоде, возможно, с исполь- зованием UTF-8):
    >>> str(x, 'utf-8')
    'xxx'
    Для реализации этого решения в текущем сценарии внесем аналогичное измене- ние в строку 53, чтобы она выглядела следующим образом:
    return str(REGEX.findall(data)[0], 'utf-8')
    После этого вывод сценария для версии Python 3 полностью совпадает с тем, что получен в сценарии Python 2:
    $ python3 bookrank3.py
    At Sun Apr 3 00:47:31 2011 on Amazon...
    - 'Python Fundamentals' ranked 969,149
    - 'Python Web Development with Django' ranked 268,660
    - 'Core Python Programming' ranked 108,796 all DONE at: Sun Apr 3 00:47:34 2011
    Вообще говоря, практика показывает, что перенос сценария из версии 2.x в вер- сию 3.x осуществляется по аналогичному принципу: необходимо убедиться, что код проходит все тесты модульности и интеграции, провести основное преобразование с использованием инструмента 2to3 (и других инструментов), а затем устранить воз- можные расхождения, добиваясь того, чтобы код успешно выполнялся и проходил такие же проверки, как и исходный сценарий. Попробуем повторить это упражнение снова на следующем примере, в котором демонстрируется использование синхрони- зации с помощью потоков.
    06_ch04.indd 212 22.01.2015 22:00:45

    213 4.7. Практическое применение многопоточной обработки
    4.7.2. Примитивы синхронизации
    В основной части этой главы рассматривались основные концепции многопоточ- ной организации и было показано, как использовать многопоточность в приложе- ниях Python. Однако в этом изложении не затрагивался один очень важный аспект многопоточного программирования: синхронизация. Довольно часто в многопоточ- ном коде содержатся определенные функции или блоки, в которых необходимо (или желательно) ограничить количество выполняемых потоков до одного. Обычно такие ситуации обнаруживаются при внесении изменений в базу данных, обновлении фай- ла или выполнении подобных действий, при которых может возникнуть состояние состязания. Как уже было сказано в этой главе, такое состояние проявляется, если код допускает появление нескольких путей выполнения или вариантов поведения либо формирование несогласованных данных, если один поток будет запущен раньше дру- гого, или наоборот. (С дополнительными сведениями о состояниях состязания мож- но ознакомиться на странице http://en.wikipedia.org/wiki/Race_condition.)
    В таких случаях возникает необходимость обеспечения синхронизации. Синхро- низация должна использоваться, если к какому-то из критических участков кода мо- гут подойти одновременно несколько потоков (см. http://en.wikipedia.org/wiki/
    Critical_section), но в каждый конкретный момент времени должно быть разре- шено дальнейшее выполнение только одного потока. Программист регламентиру- ет прохождение потоков и для управления ими выбирает подходящие примитивы синхронизации, или механизмы управления потоками, с помощью которых вводит в действие синхронизацию. Предусмотрено несколько различных методов синхрониза- ции процессов (см. http://en.wikipedia.org/wiki/Synchronization_(computer_
    science)), часть которых поддерживается языком Python. Эта поддержка предо- ставляет достаточно возможностей для выбора метода, наиболее подходящего для конкретной задачи.
    Методы синхронизации уже были представлены ранее, в начале этого раздела, по- этому перейдем к рассмотрению нескольких примеров сценариев, в которых исполь- зуются примитивы синхронизации двух типов: блокировки/мьютексы и семафоры.
    Блокировка относится к числу самых простых среди всех механизмов синхронизации и находится на самом низком уровне, а семафоры предназначены для применения в таких ситуациях, в которых несколько потоков конкурируют друг с другом, стремясь получить доступ к ограниченным ресурсам. Понять назначение блокировок проще, поэтому начнем рассмотрение примитивов синхронизации с них, а затем перейдем к семафорам.
    4.7.3. Пример применения блокировки
    Блокировки, как и следовало ожидать, имеют два состояния: заблокированное и разблокированное. Блокировки поддерживают только две функции: acquire и release. Эти функции действуют в полном соответствии с их именами — захват и освобождение.
    Иногда необходимость пройти критический участок кода возникает в нескольких потоках. В таком случае можно организовать конкуренцию между потоками за бло- кировку, и первый поток, который сможет ее захватить, получит разрешение войти в критический участок и выполнить содержащийся в нем код. Все остальные одно- временно поступающие потоки блокируются до того времени, когда первый поток завершит свою работу, выйдет из критического участка и освободит блокировку.
    06_ch04.indd 213 22.01.2015 22:00:45

    Глава 4

    Многопоточное программирование
    214
    С этого момента возможность захватить блокировку и войти в критический участок получает любой из оставшихся ожидающих потоков. Заслуживает внимания то, что отсутствует какое-либо упорядочение потоков, работа которых организована с помо- щью блокировок (т.е. применяется принцип простой очереди — “первым пришел, первым обслуживается”); процесс выбора потока-победителя не детерминирован и может зависеть даже от применяемой реализации Python.
    Рассмотрим, с чем связана необходимость применения блокировок. Сценарий mtsleepF.py представляет собой приложение, в котором происходит порождение случайным образом выбранного количества потоков, в каждом из которых осущест- вляется выход после завершения работы. Рассмотрим следующий базовый фрагмент исходного кода (для версии Python 2):
    from atexit import register
    from random import randrange
    from threading import Thread, currentThread
    from time import sleep, ctime
    class CleanOutputSet(set):
    def __str__(self):
    return ', '.join(x for x in self) loops = (randrange(2,5) for x in xrange(randrange(3,7))) remaining = CleanOutputSet()
    def loop(nsec): myname = currentThread().name remaining.add(myname)
    print '[%s] Started %s' % (ctime(), myname) sleep(nsec) remaining.remove(myname)
    print '[%s] Completed %s (%d secs)' % ( ctime(), myname, nsec)
    print ' (remaining: %s)' % (remaining or 'NONE')
    def _main():
    for pause in loops:
    Thread(target=loop, args=(pause,)).start()
    @register
    def _atexit():
    print 'all DONE at:', ctime()
    Более подробное построчное описание кода мы приведем вслед за окончательным вариантом сценария, в котором применяются блокировки, но вкратце можно отме- тить, что сценарий mtsleepF.py по сути лишь дополняет приведенные ранее приме- ры. Как и в примере сценария bookrank.py, немного упростим код. Для этого отло- жим на время применение средств объектно-ориентированного программирования, исключим список объектов потока и операции join() с потоками и снова введем в действие метод atexit.register() (по тем же причинам, как и в коде bookrank.py).
    Проведем еще одно небольшое изменение по отношению к приведенным ранее примерам mtsleepX.py. Вместо жесткого задания пары операций приостановки ци- клов/потоков на 4 и 2 секунды соответственно, внесем некую неопределенность, созда- вая случайным образом от 3 до 6 потоков, каждый из которых может приостанавли- ваться на какой-то промежуток времени от 2 до 4 секунд.
    06_ch04.indd 214 22.01.2015 22:00:46

    215 4.7. Практическое применение многопоточной обработки
    В этом сценарии применяются также некоторые новые средства, причем наиболее заметным среди них является использование множества для хранения имен остав- шихся потоков, которые все еще функционируют. Причина, по которой создается подкласс объекта множества вместо непосредственного использования самого класса, состоит в том, что это позволяет продемонстрировать еще один вариант использова- ния множества, в котором изменяется применяемое по умолчанию для вывода стро- ковое представление множества.
    При использовании операции вывода содержимого множества формируются примерно такие результаты: set([X, Y, Z,...]). Однако это не очень удобно, по- скольку потенциальные пользователи нашего приложения не знают (и не должны знать) о том, что такое множества и для чего они используются в программе. Таким образом, необходимо вместо этого вывести данные, которые выглядят примерно как
    X, Y, Z, .... Именно по этой причине мы создали подкласс класса set и реализова- ли его метод __str__().
    После этого изменения, при условии, что вся остальная часть сценария будет ра- ботать правильно, должен сформироваться аккуратный вывод, который будет иметь подходящее выравнивание:
    $ python mtsleepF.py
    [Sat Apr 2 11:37:26 2011] Started Thread-1
    [Sat Apr 2 11:37:26 2011] Started Thread-2
    [Sat Apr 2 11:37:26 2011] Started Thread-3
    [Sat Apr 2 11:37:29 2011] Completed Thread-2 (3 secs)
    (remaining: Thread-3, Thread-1)
    [Sat Apr 2 11:37:30 2011] Completed Thread-1 (4 secs)
    (remaining: Thread-3)
    [Sat Apr 2 11:37:30 2011] Completed Thread-3 (4 secs)
    (remaining: NONE) all DONE at: Sat Apr 2 11:37:30 2011
    However, if you're unlucky, you might get strange output such as this pair of example executions:
    $ python mtsleepF.py
    [Sat Apr 2 11:37:09 2011] Started Thread-1
    [Sat Apr 2 11:37:09 2011] Started Thread-2
    [Sat Apr 2 11:37:09 2011] Started Thread-3
    [Sat Apr 2 11:37:12 2011] Completed Thread-1 (3 secs)
    [Sat Apr 2 11:37:12 2011] Completed Thread-2 (3 secs)
    (remaining: Thread-3)
    (remaining: Thread-3)
    [Sat Apr 2 11:37:12 2011] Completed Thread-3 (3 secs)
    (remaining: NONE) all DONE at: Sat Apr 2 11:37:12 2011
    $ python mtsleepF.py
    [Sat Apr 2 11:37:56 2011] Started Thread-1
    [Sat Apr 2 11:37:56 2011] Started Thread-2
    [Sat Apr 2 11:37:56 2011] Started Thread-3
    [Sat Apr 2 11:37:56 2011] Started Thread-4
    [Sat Apr 2 11:37:58 2011] Completed Thread-2 (2 secs)
    [Sat Apr 2 11:37:58 2011] Completed Thread-4 (2 secs)
    (remaining: Thread-3, Thread-1)
    (remaining: Thread-3, Thread-1)
    [Sat Apr 2 11:38:00 2011] Completed Thread-1 (4 secs)
    06_ch04.indd 215 22.01.2015 22:00:46

    Глава 4

    Многопоточное программирование
    216
    (remaining: Thread-3)
    [Sat Apr 2 11:38:00 2011] Completed Thread-3 (4 secs)
    (remaining: NONE) all DONE at: Sat Apr 2 11:38:00 2011
    Что же произошло? Во-первых, очевидно, что результаты далеко не однородны
    (поскольку возможность выполнять операции ввода-вывода параллельно предостав- лена сразу нескольким потокам). Подобное чередование результирующих данных можно было также наблюдать и в некоторых примерах приведенного ранее кода.
    Во-вторых, обнаруживается такая проблема, что два потока изменяют значение од- ной и той же переменной (множества, содержащего имена оставшихся потоков).
    Операции ввода-вывода и операции доступа к одной и той же структуре данных входят в состав критических разделов кода, поэтому необходимы блокировки, ко- торые смогли бы воспрепятствовать одновременному вхождению в эти разделы не- скольких потоков. Чтобы ввести в действие блокировки, необходимо добавить стро- ку кода для импорта объекта Lock (или RLock), создать объект блокировки и внести дополнения или изменения в код, позволяющие применять блокировки в нужных местах:
    from threading import Thread, Lock, currentThread lock = Lock()
    Теперь необходимо обеспечить установку и снятие блокировки. В следующем коде показаны вызовы acquire() и release(), которые должны быть введены в функцию loop():
    def loop(nsec): myname = currentThread().name
    lock.acquire() remaining.add(myname)
    print '[%s] Started %s' % (ctime(), myname)
    lock.release() sleep(nsec)
    lock.acquire() remaining.remove(myname)
    print '[%s] Completed %s (%d secs)' % ( ctime(), myname, nsec)
    print ' (remaining: %s)' % (remaining or 'NONE')
    lock.release()
    После внесения изменений полученный вывод уже не должен содержать прежних искажений:
    $ python mtsleepF.py
    [Sun Apr 3 23:16:59 2011] Started Thread-1
    [Sun Apr 3 23:16:59 2011] Started Thread-2
    [Sun Apr 3 23:16:59 2011] Started Thread-3
    [Sun Apr 3 23:16:59 2011] Started Thread-4
    [Sun Apr 3 23:17:01 2011] Completed Thread-3 (2 secs)
    (remaining: Thread-4, Thread-2, Thread-1)
    [Sun Apr 3 23:17:01 2011] Completed Thread-4 (2 secs)
    (remaining: Thread-2, Thread-1)
    06_ch04.indd 216 22.01.2015 22:00:46

    217 4.7. Практическое применение многопоточной обработки
    [Sun Apr 3 23:17:02 2011] Completed Thread-1 (3 secs)
    (remaining: Thread-2)
    [Sun Apr 3 23:17:03 2011] Completed Thread-2 (4 secs)
    (remaining: NONE) all DONE at: Sun Apr 3 23:17:03 2011
    Исправленная (и окончательная) версия mtsleepF.py показана в примере 4.10.
    1   2   3   4   5   6   7   8


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