Чтение онлайн

ЖАНРЫ

Язык программирования Python
Шрифт:

Функции модуля threading

В модуле threading, который здесь используется, есть функции, позволяющие получить информацию о потоках:

activeCount Возвращает количество активных в настоящий момент экземпляров класса Thread. Фактически, это len(threading.enumerate).

currentThread Возвращает текущий объект–поток, то есть соответствующий потоку управления, который вызвал эту функцию. Если поток не был создан через модуль threading, будет возвращен объект–поток с сокращенной функциональностью (dummy thread object).

enumerate Возвращает список активных потоков. Завершившиеся и еще не начатые потоки не входят в список.

Класс Thread

Экземпляры

класса threading.Thread представляют потоки Python–программы. Задать действия, которые будут выполняться в потоке, можно двумя способами: передать конструктору класса исполняемый объект и аргументы к нему или путем наследования получить новый класс с переопределенным методом run. Первый способ был рассмотрен в примере выше. Конструктор класса threading.Thread имеет следующие аргументы:

Листинг

Thread(group, target, name, args, kwargs)

Здесь group — группа потоков (пока что не используется, должен быть равен None), target — объект, который будет вызван в методе run, name — имя потока, args и kwargs — последовательность и словарь позиционных и именованных параметров (соответственно) для вызова заданного в параметре target объекта. В примере выше были использованы только позиционные параметры, но то же самое можно было выполнить и с применением именованных параметров:

Листинг

import threading

def proc(n):

print «Процесс», n

p1 = threading.Thread(target=proc, name=«t1», kwargs={«n»: «1»})

p2 = threading.Thread(target=proc, name=«t2», kwargs={«n»: «2»})

p1.start

p2.start

То же самое можно проделать через наследование от класса threading.Thread с определением собственного конструктора и метода run:

Листинг

import threading

class T(threading.Thread):

def __init__(self, n):

threading.Thread.__init__(self, name=«t» + n)

self.n = n

def run(self):

print «Процесс», self.n

p1 = T(«1»)

p2 = T(«2»)

p1.start

p2.start

Самое первое, что необходимо сделать в конструкторе — вызвать конструктор базового класса. Как и раньше, для запуска потока нужно выполнить метод start объекта–потока, что приведет к выполнению действий в методе run.

Жизнью потоков можно управлять вызовом методов:

startДает потоку жизнь.

runЭтот метод представляет действия, которые должны быть выполнены в потоке.

join([timeout])Поток, который вызывает этот метод, приостанавливается, ожидая завершения потока, чей метод вызван. Параметр timeout (число с плавающей точкой) позволяет указать время ожидания (в секундах), по истечении которого приостановленный поток продолжает свою работу независимо от завершения потока, чей метод join был вызван. Вызывать join некоторого потока можно много раз. Поток не может вызвать метод join самого себя. Также нельзя ожидать завершения еще не запущенного потока. Слово «join» в переводе с английского означает «присоединить», то есть, метод, вызвавший join, желает, чтобы поток по завершении присоединился к вызывающему метод потоку.

getNameВозвращает имя потока. Для главного потока это «MainThread».

setName(name)Присваивает потоку имя name.

isAliveВозвращает истину, если поток работает (метод run уже вызван, но еще не завершился).

isDaemonВозвращает истину, если поток имеет признак демона. Программа

на Python завершается по завершении всех потоков, не являющихся демонами. Главный поток демоном не является.

setDaemon(daemonic)Устанавливает признак daemonic того, что поток является демоном. Начальное значение этого признака заимствуется у потока, запустившего данный. Признак можно изменять только для потоков, которые еще не запущены.

В модуле Thread пока что не реализованы возможности, присущие потокам в Java (определение групп потоков, приостановка и прерывание потоков извне, приоритеты и некоторые другие вещи), однако они, скорее всего, будут созданы в недалеком будущем.

Таймер

Класс threading.Timer представляет действие, которое должно быть выполнено через заданное время. Этот класс является подклассом класса threading.Thread, поэтому запускается также методом start. Следующий простой пример, печатающий на стандартном выводе Hello, world! поясняет сказанное:

Листинг

def hello:

print «Hello, world!»

t = Timer(30.0, hello)

t.start

Замки

Простейший замок может быть реализован на основе класса Lock модуля threading. Замок имеет два состояния: он может быть или открыт, или заперт. В последнем случае им владеет некоторый поток. Объект класса Lock имеет следующие методы:

acquire([blocking=True])Делает запрос на запирание замка. Если параметр blocking не указан или является истиной, то поток будет ожидать освобождения замка. Если параметр не был задан, метод не возвратит значения. Если blocking был задан и истинен, метод возвратит True (после успешного овладения замком). Если блокировка не требуется (то есть задан blocking=False), метод вернет True, если замок не был заперт и им успешно овладел данный поток. В противном случае будет возвращено False.

releaseЗапрос на отпирание замка.

lockedВозвращает текущее состояние замка (True — заперт, False — открыт). Следует иметь в виду, что даже если состояние замка только что проверено, это не означает, что он сохранит это состояние до следующей команды.

Имеется еще один вариант замка — threading.RLock, который отличается от threading.Lock тем, что некоторый поток может запрашивать его запирание много раз. Отпирание такого замка должно происходить столько же раз, сколько было запираний. Это может быть полезно, например, внутри рекурсивных функций.

Когда нужны замки?

Замки позволяют ограничивать вход в некоторую область программы одним потоком. Замки могут потребоваться для обеспечения целостности структуры данных. Например, если для корректной работы программы требуется добавление определенного элемента сразу в несколько списков или словарей, такие операции в многопоточном приложении следует обставить замками. Вокруг атомарных операций над встроенными типами (операций, которые не вызывают исполнение какого–то другого кода на Python) замки ставить необязательно. Например, метод append (встроенного) списка является атомарной операцией, а тот же метод, реализованный пользовательским классом, может требовать блокировок. В случае сомнений, конечно, лучше перестраховаться и поставить замки, однако следует минимизировать общее время действия замка, так как замок останавливает другие потоки, пытающиеся попасть в ту же область программы. Отсутствие замка в критической части программы, работающей над общими для двух и более потоков ресурсами, может привести к случайным, трудноуловимым ошибкам.

Поделиться с друзьями: