Сообщить об ошибке.

Класс Semaphore() модуля threading в Python

Управление потоками при помощи семафоров

Синтаксис:

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(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: []