Как работают блокировки и семафоры в многопроцессных приложениях?

Блокировки (Locks): используются для синхронизации доступа к разделяемым ресурсам между процессами. Только один процесс может владеть блокировкой в определенный момент времени. Другие процессы, пытающиеся получить блокировку, блокируются до тех пор, пока владелец не освободит ее. В Python для межпроцессных блокировок обычно используется `multiprocessing.Lock`.

Семафоры (Semaphores): управляют доступом к ресурсу, позволяя ограниченному числу процессов одновременно обращаться к нему. Семафор хранит внутренний счетчик. Когда процесс запрашивает семафор, счетчик уменьшается. Если счетчик равен нулю, процесс блокируется до тех пор, пока другой процесс не освободит семафор, увеличив счетчик. В Python для межпроцессных семафоров используется `multiprocessing.Semaphore`.

Важно: и блокировки, и семафоры в многопроцессной среде требуют использования механизмов, которые поддерживают межпроцессную коммуникацию (например, разделяемую память или файловые дескрипторы), чтобы обеспечить корректную синхронизацию между процессами. `multiprocessing` в Python предоставляет эти механизмы.

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

Блокировки (Locks): Представляют собой наиболее простой механизм синхронизации. Они позволяют только одному процессу в каждый момент времени владеть блокировкой. Процесс, желающий получить доступ к защищенному ресурсу, сначала пытается получить блокировку (acquire). Если блокировка свободна, процесс ее получает и продолжает работу. Если блокировка занята другим процессом, текущий процесс блокируется и ждет, пока блокировка не будет освобождена (release). После завершения работы с ресурсом, процесс освобождает блокировку, позволяя другому ожидающему процессу ее получить.

Семафоры (Semaphores): Являются более обобщенным механизмом, чем блокировки. Вместо простой "занято/свободно" блокировки, семафор имеет внутренний счетчик. Когда процесс хочет получить доступ к ресурсу, он уменьшает счетчик семафора (acquire). Если счетчик становится отрицательным, процесс блокируется до тех пор, пока счетчик не станет положительным. Когда процесс освобождает ресурс, он увеличивает счетчик семафора (release), что может разблокировать один из ожидающих процессов. Семафоры полезны, когда нужно ограничить количество процессов, одновременно получающих доступ к ресурсу (например, количество одновременных подключений к базе данных).

Различия между блокировками и семафорами:

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

Реализация в Python: В Python для многопроцессной синхронизации обычно используются классы `Lock` и `Semaphore` из модуля `multiprocessing`. Эти классы обеспечивают безопасную работу с блокировками и семафорами между процессами, используя низкоуровневые механизмы операционной системы.

Важно помнить: При использовании блокировок и семафоров в многопроцессных приложениях необходимо быть осторожным, чтобы избежать взаимных блокировок (deadlocks) и гонок данных (race conditions). Необходимо тщательно проектировать логику синхронизации и использовать контекстные менеджеры (`with lock:`), чтобы гарантировать освобождение блокировок, даже если в процессе выполнения кода произошла ошибка.

Пример с Lock:


import multiprocessing

def worker(lock, shared_data):
    with lock:  # Захватываем блокировку
        # Критическая секция: работа с общими данными
        shared_data.value += 1
        print(f"Process {multiprocessing.current_process().name}: shared_data = {shared_data.value}")

if __name__ == '__main__':
    lock = multiprocessing.Lock()
    shared_data = multiprocessing.Value('i', 0)  # 'i' обозначает integer

    processes = []
    for i in range(3):
        p = multiprocessing.Process(target=worker, args=(lock, shared_data), name=f"Process-{i}")
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

    print(f"Final shared_data value: {shared_data.value}")

    

Пример с Semaphore:


import multiprocessing
import time

def worker(semaphore, worker_id):
    with semaphore:
        print(f"Worker {worker_id} acquired semaphore.  Working...")
        time.sleep(2)  # Имитация работы
        print(f"Worker {worker_id} released semaphore.")

if __name__ == "__main__":
    semaphore = multiprocessing.Semaphore(2)  # Только 2 процесса одновременно
    processes = []

    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(semaphore, i))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

    print("All workers finished.")
     
0