import threading # обычный семафор sem = threading.Semaphore(value=1) # ограниченный семафор sem_bound = threading.BoundedSemaphore(value=1)
value=1
- начальное значение внутреннего счетчика семафора.Класс threading.Semaphore()
модуля threading
реализует семафорные объекты, которые управляют атомарным счетчиком, представляющим количество вызовов .release()
минус количество вызовов .acquire()
плюс начальное значение.
При необходимости, вызов метода .acquire()
блокируется до тех пор, пока значения внутреннего счетчика не станет положительным. Если счетчик value
не указан , значение по умолчанию равно 1.
Необязательный аргумент value
дает начальное значение для внутреннего счетчика, по умолчанию это 1. Если задано значение меньше 0, то возникает ошибка ValueError
.
Семафоры - это один из старейших примитивов синхронизации в истории информатики, изобретенный ранним голландским ученым-компьютерщиком Э́дсгером Ви́бе Де́йкстра.
Семафор управляет внутренним счетчиком, который уменьшается при каждом вызове .acquire()
и увеличивается при каждом вызове .release()
. Счетчик никогда не может опуститься ниже нуля. Когда метод .acquire()
обнаруживает, что он равен нулю, то он блокируется, ожидая, пока какой-либо другой поток не вызовет .release()
.
Класс BoundedSemaphore()
реализует ограниченные объекты семафоров. Ограниченный семафор проверяет, не превышает ли его текущее значение его начальное значение value
. Если это так, то возникает исключение ValueError
.
В большинстве случаев семафоры используются для защиты ресурсов с ограниченной емкостью. Если семафор запускается слишком много раз, то это признак ошибки.
Семафоры threading.Semaphore()
и threading.BoundedSemaphore()
также поддерживают протокол управления контекстом.
Semaphore
.Semaphore.acquire()
уменьшает значение семафора,Semaphore.release()
увеличивает значение семафора,Semaphore.acquire(blocking=True, timeout=None)
:Метод Semaphore.acquire()
поднимает семафор.
При вызове без аргументов:
True
..release()
. После пробуждения и если счетчик больше 0, то уменьшает его на 1 и возвращает True
. Каждым вызовом метода .release()
будет разбужен ровно один поток. Не следует полагаться на порядок, в котором пробуждаются потоки.При вызове с аргументом blocking=False
проверяет, если бы вызов без аргументов был заблокирован, то немедленно вернет значение False
. В противном случае сделает то же самое, что и при вызове без аргументов и вернет значение True
.
При вызове с аргументом timeout
, отличным от None
, он будет блокироваться максимум на время ожидания в секундах. Если получение не завершилось успешно в течение этого интервала, вернет значение False
. В противном случае вернет значение True
.
Semaphore.release(n=1)
:Метод Semaphore.release()
опускает семафор, увеличив внутренний счетчик на n
.
Если при входе внутренний счетчик был равен нулю, а другие потоки ждали, что он снова станет больше нуля, то разбудит n
из этих потоков.
Изменено в Python 3.9: добавлен параметр n
для одновременного освобождения нескольких ожидающих потоков.
Иногда необходимо разрешить доступ к ресурсу более чем одному потоку одновременно, ограничивая при этом общее количество потоков. Например, пул соединений может поддерживать фиксированное число одновременных подключений, или сетевое приложение может поддерживать фиксированное число одновременных загрузок. Семафор - это один из способов управления этими соединениями.
В этом примере класс ActivePool()
просто служит удобным способом отслеживать, какие потоки могут выполняться в данный момент. Реальный пул ресурсов выделит соединение или какое-то другое значение новому активному потоку и восстановит значение, когда поток будет завершен.
Здесь ActivePool()
просто хранит имена активных потоков, тем самым показывая, что в пуле могут выполняться не более двух потоков одновременно.
import threading, random, time class ActivePool: """Воображаемый пул соединений""" start = time.time() def __init__(self): super(ActivePool, self).__init__() self.active = [] self.lock = threading.Lock() def makeActive(self, name): with self.lock: self.active.append(name) tm = time.time() - self.start print(f'Время: {round(tm, 3)} Running: {self.active}') def makeInactive(self, name): with self.lock: self.active.remove(name) tm = time.time() - self.start print(f'Время: {round(tm, 3)} Running: {self.active}') def worker(sem, pool): with sem: th_name = threading.current_thread().name print(f'{th_name} ожидает присоединения к пулу') pool.makeActive(th_name) time.sleep(0.5) pool.makeInactive(th_name) # переменная семафора sem = threading.Semaphore(2) # воображаемый пул соединений pool = ActivePool() # запускаем потоки for i in range(4): t = threading.Thread( target=worker, args=(sem, pool), ) t.start() # Thread-1 ожидает присоединения к пулу # Время: 0.0 Running: ['Thread-1'] # Thread-2 ожидает присоединения к пулу # Время: 0.0 Running: ['Thread-1', 'Thread-2'] # Время: 0.501 Running: ['Thread-2'] # Thread-3 ожидает присоединения к пулу # Время: 0.501 Running: [] # Время: 0.502 Running: ['Thread-3'] # Thread-4 ожидает присоединения к пулу # Время: 0.502 Running: ['Thread-3', 'Thread-4'] # Время: 1.003 Running: ['Thread-4'] # Время: 1.003 Running: []