Как работать с семафорами в Python

В этом руководстве вы узнаете, как работать с Semaphore (семафор) в Python для управления определенным количеством потоков, которые могут одновременно обращаться к общему ресурсу.

Содержание

Что такое семафор в Python?

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

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

Semaphore поддерживает счет. Когда поток хочет получить доступ к общему ресурсу, семафор проверяет счетчик.

Если счетчик больше нуля, семафор уменьшает счетчик и позволяет потоку получить доступ к ресурсу. Если счетчик равен нулю, семафор блокирует поток до тех пор, пока счетчик не станет больше нуля.

Semaphore выполняет две основные операции:

  • Сбор: операция получения проверяет счетчик и уменьшает его, если он больше нуля. Если счетчик равен нулю, семафор будет блокировать поток до тех пор, пока другой поток не освободит семафор.
  • Освобождение: операция освобождения увеличивает счетчики, которые позволяют другим потокам получить его.

Использование семафора в Python

Чтобы использовать семафор, выполните следующие действия:

  • Сначала импортируйте модуль threading:
import threading
  • Во-вторых, создайте объект Semaphore и укажите количество потоков, которые могут получить его одновременно:
semaphore = threading.Semaphore(3)

В этом примере мы создаем объект Semaphore, который позволяет получить его одновременно только трем потокам.

  • В-третьих, получите семафор из потока, вызвав метод acquire():
semaphore.acquire()

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

  • Наконец, освободите семафор после запуска критического раздела кода, вызвав метод Release():
semaphore.release()

Чтобы гарантировать правильное получение и освобождение семафора, даже если во время выполнения критического раздела кода возникают исключения, вы можете использовать оператор with:

with semaphore:
    # Code within this block has acquired the semaphore

    # Perform operations on the shared resource
    # ...
    
# The semaphore is released outside the with block

Оператор with автоматически получает и освобождает семафор, что делает ваш код менее подверженным ошибкам.

Пример с семафором в Python

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

import threading
import urllib.request

MAX_CONCURRENT_DOWNLOADS = 3
semaphore = threading.Semaphore(MAX_CONCURRENT_DOWNLOADS)

def download(url):
    with semaphore:
        print(f"Downloading {url}...")
        
        response = urllib.request.urlopen(url)
        data = response.read()
        
        print(f"Finished downloading {url}")

        return data

        

def main():
    # URLs to download
    urls = [
        'https://www.ietf.org/rfc/rfc791.txt',
        'https://www.ietf.org/rfc/rfc792.txt',
        'https://www.ietf.org/rfc/rfc793.txt',
        'https://www.ietf.org/rfc/rfc794.txt',
        'https://www.ietf.org/rfc/rfc795.txt',
    ]

    # Create threads for each download
    threads = []
    for url in urls:
        thread = threading.Thread(target=download, args=(url,))
        threads.append(thread)
        thread.start()

    # Wait for all threads to complete
    for thread in threads:
        thread.join()


if __name__ == '__main__':
    main()

Выход:

Downloading https://www.ietf.org/rfc/rfc791.txt...
Downloading https://www.ietf.org/rfc/rfc792.txt...
Downloading https://www.ietf.org/rfc/rfc793.txt...
Finished downloading https://www.ietf.org/rfc/rfc792.txt
Downloading https://www.ietf.org/rfc/rfc794.txt...
Finished downloading https://www.ietf.org/rfc/rfc791.txt
Downloading https://www.ietf.org/rfc/rfc795.txt...
Finished downloading https://www.ietf.org/rfc/rfc793.txt
Finished downloading https://www.ietf.org/rfc/rfc794.txt
Finished downloading https://www.ietf.org/rfc/rfc795.txt

Вывод показывает, что одновременно можно загружать не более трех потоков:

Downloading https://www.ietf.org/rfc/rfc791.txt...
Downloading https://www.ietf.org/rfc/rfc792.txt...
Downloading https://www.ietf.org/rfc/rfc793.txt...

Как только количество потоков достигнет трех, следующему потоку придется дождаться освобождения семафора.

Например, ниже показано, что поток №2 завершился и освободил семафор, а следующий поток начал загрузку URL-адреса https://www.ietf.org/rfc/rfc794.txt.

Finished downloading https://www.ietf.org/rfc/rfc792.txt
Downloading https://www.ietf.org/rfc/rfc794.txt...

Как работает программа.

  • Сначала импортируйте модули threading и urlib.request:
import threading
import urllib.request
  • Во-вторых, создайте объект Semaphore для управления потоками, которые могут загружаться одновременно (их количество должно быть не больше трех):
MAX_CONCURRENT_DOWNLOADS = 3
semaphore = threading.Semaphore(MAX_CONCURRENT_DOWNLOADS)
  • В-третьих, определите функцию download(), которая загружает данные по URL-адресу и получает и освобождает семафор с помощью оператора with. Она также использует модуль urllib.request для загрузки данных с URL-адреса:
def download(url):
    with semaphore:
        print(f"Downloading {url}...")
        
        response = urllib.request.urlopen(url)
        data = response.read()
        
        print(f"Finished downloading {url}")

        return data
  • В-четвертых, определите функцию main(), которая создает пять потоков на основе списка URL-адресов и запускает их загрузку данных:
def main():
    # URLs to download
    urls = [
        'https://www.ietf.org/rfc/rfc791.txt',
        'https://www.ietf.org/rfc/rfc792.txt',
        'https://www.ietf.org/rfc/rfc793.txt',
        'https://www.ietf.org/rfc/rfc794.txt',
        'https://www.ietf.org/rfc/rfc795.txt',
    ]

    # Create threads for each download
    threads = []
    for url in urls:
        thread = threading.Thread(target=download, args=(url,))
        threads.append(thread)
        thread.start()

    # Wait for all threads to complete
    for thread in threads:
        thread.join()
  • Наконец, вызовите функцию main() в разделе if __name__ == ‘__main__’:
if __name__ == '__main__':
    main()
Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *