Как работать с семафорами в 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()