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

ЖАНРЫ

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

Тупиковая ситуация (deadlock)

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

Листинг

import threading, time

resource = {'A': threading.Lock, 'B': threading.Lock}

def proc(n, rs):

for r in rs:

print

«Процесс %s запрашивает ресурс %s» % (n, r)

resource[r].acquire

print «Процесс %s получил ресурс %s» % (n, r)

time.sleep(1)

print «Процесс %s выполняется» % n

for r in rs:

resource[r].release

print «Процесс %s закончил выполнение» % n

p1 = threading.Thread(target=proc, name=«t1», args=[«1», «AB»])

p2 = threading.Thread(target=proc, name=«t2», args=[«2», «BA»])

p1.start

p2.start

p1.join

p2.join

В этом примере два потока (t1 и t2) запрашивают замки к одним и тем же ресурсам (A и B), но в разном порядке, отчего получается, что ни у того, ни у другого не хватает ресурсов для дальнейшей работы, и они оба безнадежно повисают, ожидая освобождения нужного ресурса. Благодаря операторам print можно увидеть последовательность событий:

Листинг

Процесс 1 запрашивает ресурс A

Процесс 1 получил ресурс A

Процесс 2 запрашивает ресурс B

Процесс 2 получил ресурс B

Процесс 1 запрашивает ресурс B

Процесс 2 запрашивает ресурс A

Существуют методики, позволяющие избежать подобных тупиков, однако их рассмотрение не входит в рамки данной лекции. Можно посоветовать следующие приемы:

построить логику приложения так, чтобы никогда не запрашивать замки к двум ресурсам сразу. Возможно, придется определить составной ресурс. В частности, к данному примеру можно было бы определить замок «AB» для указания эксклюзивного доступа к ресурсам A и B.

строго упорядочить все ресурсы (например, по цене) и всегда запрашивать их в определенном порядке (скажем, начиная с более дорогих ресурсов). При этом перед заказом некоторого ресурса поток должен отказаться от заблокированных им более дешевых ресурсов.

Семафоры

Семафоры (их иногда называют семафорами Дийкстры (Dijkstra) по имени их изобретателя) являются более общим механизмом синхронизации потоков, нежели замки. Семафоры могут допустить в критическую область программы сразу несколько потоков. Семафор имеет счетчик запросов, уменьшающийся с каждым вызовом метода acquire и увеличивающийся при каждом вызове release. Счетчик не может стать меньше нуля, поэтому в таком состоянии потокам приходится ждать, как и в случае с замками, пока значение счетчика не увеличится.

Конструктор класса threading.Semaphore принимает в качестве (необязательного) аргумента начальное состояние счетчика (по умолчанию оно равно 1, что соответствует замку класса Lock). Методы acquire и release действуют аналогично описанным выше одноименным методам у замков.

Семафор может применяться для охраны ограниченного ресурса. Например, с его помощью можно вести пул соединений с базой данных. Пример такого

использования семафора (заимствован из документации к Python) дан ниже:

Листинг

from threading import BoundedSemaphore

maxconnections = 5

# Подготовка семафора

pool_sema = BoundedSemaphore(value=maxconnections)

# Внутри потока:

pool_sema.acquire

conn = connectdb

# … использование соединения …

conn.close

pool_sema.release

Таким образом, применяется не более пяти соединений с базой данных. В примере использован класс threading.BoundedSemaphore. Экземпляры этого класса отличаются от экземпляров класса threading.Semaphore тем, что не дают сделать release больше, чем сделан acquire.

События

Еще одним способом коммуникации между объектами являются события. Экземпляры класса threading.Event могут быть использованы для передачи информации о наступлении некоторого события от одного потока одному или нескольким другим потокам. Объекты–события имеют внутренний флаг, который может находиться в установленном или сброшенном состоянии. При своем создании флаг события находится в сброшенном состоянии. Если флаг в установленном состоянии, ожидания не происходит: поток, вызвавший метод wait для ожидания события, просто продолжает свою работу. Ниже приведены методы экземпляров класса threading.Event:

setУстанавливает внутренний флаг, сигнализирующий о наступлении события. Все ждущие данного события потоки выходят из состояния ожидания.

clearСбрасывает флаг. Все события, которые вызывают метод wait этого объекта–события, будут находиться в состоянии ожидания до тех пор, пока флаг сброшен, или по истечении заданного таймаута.

isSetВозвращает состояние флага.

wait([timeout])Переводит поток в состояние ожидания, если флаг сброшен, и сразу возвращается, если флаг установлен. Аргумент timeout задает таймаут в секундах, по истечении которого ожидание прекращается, даже если событие не наступило.

Составить пример работы с событиями предлагается в качестве упражнения.

Условия

Более сложным механизмом коммуникации между потоками является механизм условий. Условия представляются в виде экземпляров класса threading.Condition и, подобно только что рассмотренным событиям, оповещают потоки об изменении некоторого состояния. Конструктор класса threading.Condition принимает необязательный параметр, задающий замок класса threading.Lock или threading.RLock. По умолчанию создается новый экземпляр замка класса threading.RLock. Методы объекта–условия описаны ниже:

acquire(…)Запрашивает замок. Фактически вызывается одноименный метод принадлежащего объекту–условию объекта–замка.

releaseСнимает замок.

wait([timeout])Переводит поток в режим ожидания. Этот метод может быть вызван только в том случае, если вызывающий его поток получил замок. Метод снимает замок и блокирует поток до появления объявлений, то есть вызовов методов notify и notifyAll другими потоками. Необязательный аргумент timeout задает таймаут ожидания в секундах. При выходе из ожидания поток снова запрашивает замок и возвращается из метода wait.

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